feat: 接口定义关系图

This commit is contained in:
chenjianxing 2021-10-19 15:48:53 +08:00 committed by jianxing
parent 527595e85a
commit 18848277f4
36 changed files with 1097 additions and 449 deletions

View File

@ -14,10 +14,7 @@ import io.metersphere.api.service.ApiDefinitionService;
import io.metersphere.api.service.ApiTestEnvironmentService;
import io.metersphere.api.service.EsbApiParamService;
import io.metersphere.api.service.EsbImportService;
import io.metersphere.base.domain.ApiDefinition;
import io.metersphere.base.domain.ApiDefinitionWithBLOBs;
import io.metersphere.base.domain.ApiTestEnvironmentWithBLOBs;
import io.metersphere.base.domain.Schedule;
import io.metersphere.base.domain.*;
import io.metersphere.commons.constants.NoticeConstants;
import io.metersphere.commons.constants.OperLogConstants;
import io.metersphere.commons.constants.PermissionConstants;
@ -26,6 +23,7 @@ import io.metersphere.commons.utils.PageUtils;
import io.metersphere.commons.utils.Pager;
import io.metersphere.controller.request.ResetOrderRequest;
import io.metersphere.controller.request.ScheduleRequest;
import io.metersphere.dto.RelationshipEdgeDTO;
import io.metersphere.log.annotation.MsAuditLog;
import io.metersphere.notice.annotation.SendNotice;
import io.metersphere.service.CheckPermissionService;
@ -305,4 +303,15 @@ public class ApiDefinitionController {
public void orderCase(@RequestBody ResetOrderRequest request) {
apiDefinitionService.updateOrder(request);
}
@GetMapping("/relationship/{id}/{relationshipType}")
public List<RelationshipEdgeDTO> getRelationshipApi(@PathVariable("id") String id, @PathVariable("relationshipType") String relationshipType) {
return apiDefinitionService.getRelationshipApi(id, relationshipType);
}
@PostMapping("/relationship/relate/{goPage}/{pageSize}")
public Pager< List<ApiDefinitionResult>> getRelationshipRelateList(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody ApiDefinitionRequest request) {
Page<Object> page = PageHelper.startPage(goPage, pageSize, true);
return PageUtils.setPageInfo(page, apiDefinitionService.getRelationshipRelateList(request));
}
}

View File

@ -44,6 +44,8 @@ public class SaveApiDefinitionRequest {
private String followPeople;
private String remark;
private Schedule schedule;
private String triggerMode;

View File

@ -4,5 +4,5 @@ import io.metersphere.base.domain.ApiScenarioWithBLOBs;
public interface ApiAutomationRelationshipEdgeService {
// 初始化引用关系
public void initRelationshipEdge(ApiScenarioWithBLOBs before, ApiScenarioWithBLOBs now);
void initRelationshipEdge(ApiScenarioWithBLOBs before, ApiScenarioWithBLOBs now);
}

View File

@ -172,7 +172,7 @@ public class ApiAutomationService {
public List<ApiScenarioWithBLOBs> listAll(ApiScenarioBatchRequest request) {
ServiceUtils.getSelectAllIds(request, request.getCondition(),
(query) -> extApiScenarioMapper.selectIdsByQuery((ApiScenarioRequest) query));
(query) -> extApiScenarioMapper.selectIdsByQuery(query));
List<ApiScenarioWithBLOBs> list = extApiScenarioMapper.selectIds(request.getIds());
return list;
}
@ -183,7 +183,7 @@ public class ApiAutomationService {
public List<String> idAll(ApiScenarioBatchRequest request) {
ServiceUtils.getSelectAllIds(request, request.getCondition(),
(query) -> extApiScenarioMapper.selectIdsByQuery((ApiScenarioRequest) query));
(query) -> extApiScenarioMapper.selectIdsByQuery(query));
return request.getIds();
}
@ -1004,7 +1004,7 @@ public class ApiAutomationService {
*/
public String modeRun(RunScenarioRequest request) {
ServiceUtils.getSelectAllIds(request, request.getCondition(),
(query) -> extApiScenarioMapper.selectIdsByQuery((ApiScenarioRequest) query));
(query) -> extApiScenarioMapper.selectIdsByQuery(query));
List<String> ids = request.getIds();
// 生成集成报告
@ -1454,7 +1454,7 @@ public class ApiAutomationService {
*/
public String execute(RunScenarioRequest request) {
ServiceUtils.getSelectAllIds(request, request.getCondition(),
(query) -> extApiScenarioMapper.selectIdsByQuery((ApiScenarioRequest) query));
(query) -> extApiScenarioMapper.selectIdsByQuery(query));
List<String> ids = request.getIds();
//检查是否有正在执行中的情景
// this.checkScenarioIsRunning(ids);
@ -1770,7 +1770,7 @@ public class ApiAutomationService {
public void bathEdit(ApiScenarioBatchRequest request) {
ServiceUtils.getSelectAllIds(request, request.getCondition(),
(query) -> extApiScenarioMapper.selectIdsByQuery((ApiScenarioRequest) query));
(query) -> extApiScenarioMapper.selectIdsByQuery(query));
if (StringUtils.isNotBlank(request.getEnvironmentId())) {
bathEditEnv(request);
@ -2027,7 +2027,7 @@ public class ApiAutomationService {
private List<ApiScenarioWithBLOBs> getExportResult(ApiScenarioBatchRequest request) {
ServiceUtils.getSelectAllIds(request, request.getCondition(),
(query) -> extApiScenarioMapper.selectIdsByQuery((ApiScenarioRequest) query));
(query) -> extApiScenarioMapper.selectIdsByQuery(query));
ApiScenarioExample example = new ApiScenarioExample();
example.createCriteria().andIdIn(request.getIds());
List<ApiScenarioWithBLOBs> apiScenarioWithBLOBs = apiScenarioMapper.selectByExampleWithBLOBs(example);
@ -2145,14 +2145,14 @@ public class ApiAutomationService {
public void removeToGcByBatch(ApiScenarioBatchRequest request) {
ServiceUtils.getSelectAllIds(request, request.getCondition(),
(query) -> extApiScenarioMapper.selectIdsByQuery((ApiScenarioRequest) query));
(query) -> extApiScenarioMapper.selectIdsByQuery(query));
this.removeToGc(request.getIds());
}
public void deleteBatchByCondition(ApiScenarioBatchRequest request) {
ServiceUtils.getSelectAllIds(request, request.getCondition(),
(query) -> extApiScenarioMapper.selectIdsByQuery((ApiScenarioRequest) query));
(query) -> extApiScenarioMapper.selectIdsByQuery(query));
this.deleteBatch(request.getIds());
}
@ -2367,7 +2367,7 @@ public class ApiAutomationService {
request.setIds(new ArrayList<>(0));
}
ServiceUtils.getSelectAllIds(request, request.getCondition(),
(query) -> extApiScenarioMapper.selectIdsByQuery((ApiScenarioRequest) query));
(query) -> extApiScenarioMapper.selectIdsByQuery(query));
if (!request.getIds().isEmpty()) {
ApiScenarioExample example = new ApiScenarioExample();
@ -2400,7 +2400,7 @@ public class ApiAutomationService {
public List<ApiScenarioWithBLOBs> listWithIds(ApiScenarioBatchRequest request) {
ServiceUtils.getSelectAllIds(request, request.getCondition(),
(query) -> extApiScenarioMapper.selectIdsByQuery((ApiScenarioRequest) query));
(query) -> extApiScenarioMapper.selectIdsByQuery(query));
List<ApiScenarioWithBLOBs> list = extApiScenarioMapper.listWithIds(request.getIds());
return list;
}
@ -2470,7 +2470,7 @@ public class ApiAutomationService {
public List<JmxInfoDTO> batchGenPerformanceTestJmx(ApiScenarioBatchRequest request) {
ServiceUtils.getSelectAllIds(request, request.getCondition(),
(query) -> extApiScenarioMapper.selectIdsByQuery((ApiScenarioRequest) query));
(query) -> extApiScenarioMapper.selectIdsByQuery(query));
List<JmxInfoDTO> returnList = new ArrayList<>();
List<String> ids = request.getIds();
@ -2495,7 +2495,7 @@ public class ApiAutomationService {
public BatchOperaResponse batchCopy(ApiScenarioBatchRequest batchRequest) {
ServiceUtils.getSelectAllIds(batchRequest, batchRequest.getCondition(),
(query) -> extApiScenarioMapper.selectIdsByQuery((ApiScenarioRequest) query));
(query) -> extApiScenarioMapper.selectIdsByQuery(query));
List<ApiScenarioWithBLOBs> apiScenarioList = extApiScenarioMapper.selectIds(batchRequest.getIds());
StringBuffer stringBuffer = new StringBuffer();
for (ApiScenarioWithBLOBs apiModel : apiScenarioList) {
@ -2555,7 +2555,7 @@ public class ApiAutomationService {
public DeleteCheckResult checkBeforeDelete(ApiScenarioBatchRequest request) {
ServiceUtils.getSelectAllIds(request, request.getCondition(),
(query) -> extApiScenarioMapper.selectIdsByQuery((ApiScenarioRequest) query));
(query) -> extApiScenarioMapper.selectIdsByQuery(query));
List<String> deleteIds = request.getIds();
DeleteCheckResult result = new DeleteCheckResult();
List<String> checkMsgList = new ArrayList<>();

View File

@ -33,6 +33,7 @@ import io.metersphere.commons.utils.*;
import io.metersphere.controller.request.ResetOrderRequest;
import io.metersphere.controller.request.ScheduleRequest;
import io.metersphere.dto.BaseSystemConfigDTO;
import io.metersphere.dto.RelationshipEdgeDTO;
import io.metersphere.i18n.Translator;
import io.metersphere.job.sechedule.SwaggerUrlImportJob;
import io.metersphere.log.utils.ReflexObjectUtil;
@ -43,6 +44,7 @@ import io.metersphere.log.vo.api.DefinitionReference;
import io.metersphere.notice.sender.NoticeModel;
import io.metersphere.notice.service.NoticeSendService;
import io.metersphere.service.FileService;
import io.metersphere.service.RelationshipEdgeService;
import io.metersphere.service.ScheduleService;
import io.metersphere.service.SystemParameterService;
import io.metersphere.track.request.testcase.ApiCaseRelevanceRequest;
@ -115,6 +117,8 @@ public class ApiDefinitionService {
private NoticeSendService noticeSendService;
@Resource
private ExtApiTestCaseMapper extApiTestCaseMapper;
@Resource
private RelationshipEdgeService relationshipEdgeService;
private static Cache cache = Cache.newHardMemoryCache(0, 3600);
@ -444,6 +448,7 @@ public class ApiDefinitionService {
test.setResponse(JSONObject.toJSONString(request.getResponse()));
test.setEnvironmentId(request.getEnvironmentId());
test.setUserId(request.getUserId());
test.setRemark(request.getRemark());
test.setFollowPeople(request.getFollowPeople());
if (StringUtils.isNotEmpty(request.getTags()) && !StringUtils.equals(request.getTags(), "[]")) {
test.setTags(request.getTags());
@ -482,6 +487,7 @@ public class ApiDefinitionService {
test.setModulePath(request.getModulePath());
test.setModuleId(request.getModuleId());
test.setFollowPeople(request.getFollowPeople());
test.setRemark(request.getRemark());
test.setOrder(ServiceUtils.getNextOrder(request.getProjectId(), extApiDefinitionMapper::getLastOrder));
if (StringUtils.isEmpty(request.getModuleId()) || "default-module".equals(request.getModuleId())) {
ApiModuleExample example = new ApiModuleExample();
@ -1560,4 +1566,41 @@ public class ApiDefinitionService {
public long countQuotedApiByProjectId(String projectId) {
return extApiDefinitionMapper.countQuotedApiByProjectId(projectId);
}
public List<RelationshipEdgeDTO> getRelationshipApi(String id, String relationshipType) {
List<RelationshipEdge> relationshipEdges= relationshipEdgeService.getRelationshipEdgeByType(id, relationshipType);
List<String> ids = relationshipEdgeService.getRelationIdsByType(relationshipType, relationshipEdges);
if (CollectionUtils.isNotEmpty(ids)) {
ApiDefinitionExample example = new ApiDefinitionExample();
example.createCriteria().andIdIn(ids);
List<ApiDefinition> apiDefinitions = apiDefinitionMapper.selectByExample(example);
Map<String, ApiDefinition> apiMap = apiDefinitions.stream().collect(Collectors.toMap(ApiDefinition::getId, i -> i));
List<RelationshipEdgeDTO> results = new ArrayList<>();
for (RelationshipEdge relationshipEdge : relationshipEdges) {
RelationshipEdgeDTO relationshipEdgeDTO = new RelationshipEdgeDTO();
BeanUtils.copyBean(relationshipEdgeDTO, relationshipEdge);
ApiDefinition apiDefinition;
if (StringUtils.equals(relationshipType, "PRE")) {
apiDefinition = apiMap.get(relationshipEdge.getTargetId());
} else {
apiDefinition = apiMap.get(relationshipEdge.getSourceId());
}
relationshipEdgeDTO.setTargetName(apiDefinition.getName());
relationshipEdgeDTO.setCreator(apiDefinition.getCreateUser());
relationshipEdgeDTO.setTargetNum(apiDefinition.getNum());
results.add(relationshipEdgeDTO);
}
return results;
}
return new ArrayList<>();
}
public List<ApiDefinitionResult> getRelationshipRelateList(ApiDefinitionRequest request) {
request = this.initRequest(request, true, true);
List<String> relationshipIds = relationshipEdgeService.getRelationshipIds(request.getId());
request.setNotInIds(relationshipIds);
request.setId(null); // 去掉id的查询条件
return extApiDefinitionMapper.list(request);
}
}

View File

@ -15,5 +15,7 @@ public class ApiDefinitionWithBLOBs extends ApiDefinition implements Serializabl
private String response;
private String remark;
private static final long serialVersionUID = 1L;
}

View File

@ -32,6 +32,7 @@
<result column="description" jdbcType="LONGVARCHAR" property="description" />
<result column="request" jdbcType="LONGVARCHAR" property="request" />
<result column="response" jdbcType="LONGVARCHAR" property="response" />
<result column="remark" jdbcType="LONGVARCHAR" property="remark" />
</resultMap>
<sql id="Example_Where_Clause">
<where>
@ -98,7 +99,7 @@
follow_people, `order`
</sql>
<sql id="Blob_Column_List">
description, request, response
description, request, response, remark
</sql>
<select id="selectByExampleWithBLOBs" parameterType="io.metersphere.base.domain.ApiDefinitionExample" resultMap="ResultMapWithBLOBs">
select
@ -158,7 +159,7 @@
case_total, case_status, case_passing_rate,
delete_time, delete_user_id, follow_people,
`order`, description, request,
response)
response, remark)
values (#{id,jdbcType=VARCHAR}, #{projectId,jdbcType=VARCHAR}, #{name,jdbcType=VARCHAR},
#{method,jdbcType=VARCHAR}, #{protocol,jdbcType=VARCHAR}, #{path,jdbcType=VARCHAR},
#{modulePath,jdbcType=VARCHAR}, #{environmentId,jdbcType=VARCHAR}, #{schedule,jdbcType=VARCHAR},
@ -168,7 +169,7 @@
#{caseTotal,jdbcType=VARCHAR}, #{caseStatus,jdbcType=VARCHAR}, #{casePassingRate,jdbcType=VARCHAR},
#{deleteTime,jdbcType=BIGINT}, #{deleteUserId,jdbcType=VARCHAR}, #{followPeople,jdbcType=VARCHAR},
#{order,jdbcType=BIGINT}, #{description,jdbcType=LONGVARCHAR}, #{request,jdbcType=LONGVARCHAR},
#{response,jdbcType=LONGVARCHAR})
#{response,jdbcType=LONGVARCHAR}, #{remark,jdbcType=LONGVARCHAR})
</insert>
<insert id="insertSelective" parameterType="io.metersphere.base.domain.ApiDefinitionWithBLOBs">
insert into api_definition
@ -257,6 +258,9 @@
<if test="response != null">
response,
</if>
<if test="remark != null">
remark,
</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="id != null">
@ -343,6 +347,9 @@
<if test="response != null">
#{response,jdbcType=LONGVARCHAR},
</if>
<if test="remark != null">
#{remark,jdbcType=LONGVARCHAR},
</if>
</trim>
</insert>
<select id="countByExample" parameterType="io.metersphere.base.domain.ApiDefinitionExample" resultType="java.lang.Long">
@ -438,6 +445,9 @@
<if test="record.response != null">
response = #{record.response,jdbcType=LONGVARCHAR},
</if>
<if test="record.remark != null">
remark = #{record.remark,jdbcType=LONGVARCHAR},
</if>
</set>
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
@ -472,7 +482,8 @@
`order` = #{record.order,jdbcType=BIGINT},
description = #{record.description,jdbcType=LONGVARCHAR},
request = #{record.request,jdbcType=LONGVARCHAR},
response = #{record.response,jdbcType=LONGVARCHAR}
response = #{record.response,jdbcType=LONGVARCHAR},
remark = #{record.remark,jdbcType=LONGVARCHAR}
<if test="_parameter != null">
<include refid="Update_By_Example_Where_Clause" />
</if>
@ -592,6 +603,9 @@
<if test="response != null">
response = #{response,jdbcType=LONGVARCHAR},
</if>
<if test="remark != null">
remark = #{remark,jdbcType=LONGVARCHAR},
</if>
</set>
where id = #{id,jdbcType=VARCHAR}
</update>
@ -623,7 +637,8 @@
`order` = #{order,jdbcType=BIGINT},
description = #{description,jdbcType=LONGVARCHAR},
request = #{request,jdbcType=LONGVARCHAR},
response = #{response,jdbcType=LONGVARCHAR}
response = #{response,jdbcType=LONGVARCHAR},
remark = #{remark,jdbcType=LONGVARCHAR}
where id = #{id,jdbcType=VARCHAR}
</update>
<update id="updateByPrimaryKey" parameterType="io.metersphere.base.domain.ApiDefinition">

View File

@ -6,13 +6,14 @@ import io.metersphere.api.dto.definition.ApiDefinitionRequest;
import io.metersphere.api.dto.definition.ApiDefinitionResult;
import io.metersphere.api.dto.definition.ApiSwaggerUrlDTO;
import io.metersphere.base.domain.ApiDefinition;
import io.metersphere.base.domain.ApiDefinitionExample;
import io.metersphere.base.domain.ApiDefinitionExampleWithOperation;
import io.metersphere.controller.request.BaseQueryRequest;
import io.metersphere.dto.RelationshipGraphData;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;
import java.util.Set;
public interface ExtApiDefinitionMapper {
List<ApiSwaggerUrlDTO> selectScheduleList(@Param("projectId") String projectId);
@ -68,4 +69,6 @@ public interface ExtApiDefinitionMapper {
Long getLastOrder(@Param("projectId")String projectId, @Param("baseOrder") Long baseOrder);
long countQuotedApiByProjectId(String projectId);
List<RelationshipGraphData.Node> getForGraph(@Param("ids") Set<String> ids);
}

View File

@ -238,7 +238,7 @@
api_definition.name,api_definition.protocol,api_definition.path,api_definition.module_id,api_definition.module_path,api_definition.method,
api_definition.description,api_definition.request,api_definition.response,api_definition.environment_id,
api_definition.status, api_definition.user_id, api_definition.create_time, api_definition.update_time, project.name as
project_name, user.name as user_name,deleteUser.name AS delete_user,api_definition.delete_time
project_name, user.name as user_name,deleteUser.name AS delete_user,api_definition.delete_time, api_definition.remark
from api_definition
left join project on api_definition.project_id = project.id
left join user on api_definition.user_id = user.id
@ -602,6 +602,12 @@
<if test="request.moduleId != null">
AND api_definition.module_id = #{request.moduleId}
</if>
<if test="request.notInIds != null and request.notInIds.size() > 0">
and api_definition.id not in
<foreach collection="request.notInIds" item="id" separator="," open="(" close=")">
#{id}
</foreach>
</if>
<choose>
<when test="request.moduleIds != null and request.moduleIds.size() > 0">
AND api_definition.module_id in
@ -667,5 +673,14 @@
)
)
</select>
<select id="getForGraph" resultType="io.metersphere.dto.RelationshipGraphData$Node">
select id,num,`name`
from api_definition
where id in
<foreach collection="ids" item="id" separator="," open="(" close=")">
#{id}
</foreach>
and api_definition.status != 'Trash';
</select>
</mapper>

View File

@ -6,6 +6,7 @@ import io.metersphere.api.dto.datacount.ApiDataCountResult;
import io.metersphere.base.domain.ApiScenario;
import io.metersphere.base.domain.ApiScenarioExampleWithOperation;
import io.metersphere.base.domain.ApiScenarioWithBLOBs;
import io.metersphere.controller.request.BaseQueryRequest;
import io.metersphere.dto.RelationshipGraphData;
import org.apache.ibatis.annotations.Param;
@ -52,7 +53,7 @@ public interface ExtApiScenarioMapper {
ApiScenario getNextNum(@Param("projectId") String projectId);
List<String> selectIdsByQuery(@Param("request") ApiScenarioRequest request);
List<String> selectIdsByQuery(@Param("request") BaseQueryRequest request);
void updateCustomNumByProjectId(@Param("projectId") String projectId);

View File

@ -24,6 +24,11 @@ public class BaseQueryRequest {
private List<String> nodeIds;
/**
* 排除哪些id
*/
private List<String> notInIds;
/**
* selectAll选择的数据是否是全部数据全部数据是不受分页影响的数据
* filters: 数据状态

View File

@ -7,6 +7,7 @@ import io.metersphere.base.mapper.RelationshipEdgeMapper;
import io.metersphere.commons.utils.SessionUtils;
import io.metersphere.controller.request.RelationshipEdgeRequest;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
@ -17,6 +18,7 @@ import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
/**
* @author jianxingChen
@ -57,6 +59,28 @@ public class RelationshipEdgeService {
relationshipEdgeMapper.deleteByExample(example);
}
public List<RelationshipEdge> getRelationshipEdgeByType(String id, String relationshipType) {
if (StringUtils.equals(relationshipType, "PRE")) {
return getBySourceId(id);
} else if (StringUtils.equals(relationshipType, "POST")) {
return getByTargetId(id);
}
return new ArrayList<>();
}
public List<String> getRelationIdsByType(String relationshipType, List<RelationshipEdge> relationshipEdges) {
if (StringUtils.equals(relationshipType, "PRE")) {
return relationshipEdges.stream()
.map(RelationshipEdge::getTargetId)
.collect(Collectors.toList());
} else if (StringUtils.equals(relationshipType, "POST")) {
return relationshipEdges.stream()
.map(RelationshipEdge::getSourceId)
.collect(Collectors.toList());
}
return new ArrayList<>();
}
public List<RelationshipEdge> getBySourceId(String sourceId) {
RelationshipEdgeExample example = new RelationshipEdgeExample();
example.createCriteria()
@ -140,4 +164,18 @@ public class RelationshipEdgeService {
return graphId;
}
/**
* 给定一个节点获取跟他关联的所有节点的id
* @param nodeId
* @return
*/
public List<String> getRelationshipIds(String nodeId) {
List<RelationshipEdge> sourceRelationshipEdges = getBySourceId(nodeId);
List<RelationshipEdge> targetRelationshipEdges = getByTargetId(nodeId);
List<String> ids = sourceRelationshipEdges.stream().map(RelationshipEdge::getTargetId).collect(Collectors.toList());
ids.addAll(targetRelationshipEdges.stream().map(RelationshipEdge::getSourceId).collect(Collectors.toList()));
ids.add(nodeId);
return ids;
}
}

View File

@ -1959,29 +1959,15 @@ public class TestCaseService {
public List<TestCase> getRelationshipRelateList(QueryTestCaseRequest request) {
setDefaultOrder(request);
List<RelationshipEdge> sourceRelationshipEdges = relationshipEdgeService.getBySourceId(request.getId());
List<RelationshipEdge> targetRelationshipEdges = relationshipEdgeService.getByTargetId(request.getId());
List<String> ids = sourceRelationshipEdges.stream().map(RelationshipEdge::getTargetId).collect(Collectors.toList());
ids.addAll(targetRelationshipEdges.stream().map(RelationshipEdge::getTargetId).collect(Collectors.toList()));
ids.add(request.getId());
request.setTestCaseContainIds(ids);
List<String> relationshipIds = relationshipEdgeService.getRelationshipIds(request.getId());
request.setTestCaseContainIds(relationshipIds);
return extTestCaseMapper.getTestCase(request);
}
public List<RelationshipEdgeDTO> getRelationshipCase(String id, String relationshipType) {
List<RelationshipEdge> relationshipEdges;
List<String> ids;
if (StringUtils.equals(relationshipType, "PRE")) {
relationshipEdges = relationshipEdgeService.getBySourceId(id);
ids = relationshipEdges.stream()
.map(RelationshipEdge::getTargetId)
.collect(Collectors.toList());
} else {
relationshipEdges = relationshipEdgeService.getByTargetId(id);
ids = relationshipEdges.stream()
.map(RelationshipEdge::getSourceId)
.collect(Collectors.toList());
}
List<RelationshipEdge> relationshipEdges= relationshipEdgeService.getRelationshipEdgeByType(id, relationshipType);
List<String> ids = relationshipEdgeService.getRelationIdsByType(relationshipType, relationshipEdges);
if (CollectionUtils.isNotEmpty(ids)) {
TestCaseExample example = new TestCaseExample();

@ -1 +1 @@
Subproject commit 77d0367751b0a996a32f1c4b6b18bee9becfc4e3
Subproject commit 7fa58311f24417999905d463f17dc142963c3524

View File

@ -51,6 +51,8 @@ create table if not exists relationship_edge (
DEFAULT CHARSET = utf8mb4
COLLATE utf8mb4_general_ci;
ALTER TABLE api_definition ADD remark TEXT NULL;
ALTER TABLE test_case_review ADD COLUMN follow_people;
ALTER TABLE test_plan ADD COLUMN follow_people;

View File

@ -446,7 +446,8 @@ export default {
{
name: "生成依赖关系",
handleClick: this.generateGraph,
permissions: ['PROJECT_API_SCENARIO:READ+MOVE_BATCH']
isXPack: true,
permissions: ['PROJECT_API_SCENARIO:READ+EDIT']
},
{
name: this.$t('api_test.automation.batch_add_plan'),
@ -492,9 +493,14 @@ export default {
};
},
created() {
if (!hasLicense()) {
this.unTrashButtons.splice(5,1);
}
// if (!hasLicense()) {
// for (let i = 0; i < this.unTrashButtons.length; i++) {
// if (this.unTrashButtons[i].handleClick === this.generateGraph) {
// this.unTrashButtons.splice(i,1);
// break;
// }
// }
// }
scenario.$on('hide', id => {
this.hideStopBtn(id);
});

View File

@ -4,100 +4,28 @@
:is-api-list-enable="isApiListEnable"
@isApiListEnableChange="isApiListEnableChange">
<ms-environment-select :project-id="projectId" v-if="isTestPlan" :is-read-only="isReadOnly"
@setEnvironment="setEnvironment" ref="msEnvironmentSelect"/>
<el-input :placeholder="$t('commons.search_by_name_or_id')" @blur="initTable" class="search-input" size="small"
@keyup.enter.native="initTable" v-model="condition.name"/>
<ms-table-adv-search-bar :condition.sync="condition" class="adv-search-bar"
v-if="condition.components !== undefined && condition.components.length > 0"
@search="initTable"/>
<ms-table :data="tableData" :select-node-ids="selectNodeIds" :condition="condition" :page-size="pageSize"
:total="total" enableSelection
:screenHeight="screenHeight"
operator-width="170px"
@refresh="initTable"
<api-table-list
:table-data="tableData"
:condition="condition"
:select-node-ids="selectNodeIds"
:result="result"
:current-protocol="currentProtocol"
:current-page="currentPage"
:page-size="pageSize"
:screen-height="screenHeight"
@setSelectRow="setSelectRow"
@refreshTable="initTable"
ref="apitable">
<ms-table-column
prop="num"
label="ID"
min-width="80px"
sortable>
<!-- <template slot-scope="scope">-->
<!-- &lt;!&ndash; 判断为只读用户的话不可点击ID进行编辑操作 &ndash;&gt;-->
<!-- <span style="cursor:pointer" v-if="isReadOnly"> {{ scope.row.num }} </span>-->
<!-- <el-tooltip v-else content="编辑">-->
<!-- <a style="cursor:pointer" @click="editApi(scope.row)"> {{ scope.row.num }} </a>-->
<!-- </el-tooltip>-->
<!-- </template>-->
</ms-table-column>
<ms-table-column
prop="name"
:label="$t('api_test.definition.api_name')"
sortable
width="120px"/>
<ms-table-column
prop="method"
sortable="custom"
column-key="method"
:filters="methodFilters"
:label="getApiRequestTypeName"
width="120px">
<template v-slot:default="scope">
<el-tag size="mini"
:style="{'background-color': getColor(true, scope.row.method), border: getColor(true, scope.row.method)}"
class="api-el-tag">
{{ scope.row.method }}
</el-tag>
<template v-slot:header>
<ms-environment-select :project-id="projectId" v-if="isTestPlan" :is-read-only="isReadOnly"
@setEnvironment="setEnvironment" ref="msEnvironmentSelect"/>
</template>
</ms-table-column>
<ms-table-column
prop="userName"
sortable="custom"
:filters="userFilters"
column-key="user_id"
:label="$t('api_test.definition.api_principal')"
width="100px"/>
</api-table-list>
<ms-table-column
prop="path"
width="120px"
:label="$t('api_test.definition.api_path')"/>
<ms-table-column
prop="tags"
:label="$t('commons.tag')"
width="120px">
<template v-slot:default="scope">
<ms-tag v-for="(itemName,index) in scope.row.tags" :key="index" type="success" effect="plain"
:show-tooltip="true" :content="itemName"
style="margin-left: 0px; margin-right: 2px"/>
</template>
</ms-table-column>
<ms-table-column
width="160"
:label="$t('api_test.definition.api_last_time')"
sortable="custom"
prop="updateTime">
<template v-slot:default="scope">
<span>{{ scope.row.updateTime | timestampFormatDate }}</span>
</template>
</ms-table-column>
<ms-table-column
prop="caseTotal"
width="80px"
:label="$t('api_test.definition.api_case_number')"/>
</ms-table>
<ms-table-pagination :change="initTable" :current-page.sync="currentPage" :page-size.sync="pageSize"
:total="total"/>
</api-list-container>
<table-select-count-bar :count="selectRows.size"/>
</div>
@ -105,88 +33,35 @@
<script>
import MsTable from "@/business/components/common/components/table/MsTable";
import MsTableColumn from "@/business/components/common/components/table/MsTableColumn";
import MsTableOperator from "../../../../common/components/MsTableOperator";
import MsTableOperatorButton from "../../../../common/components/MsTableOperatorButton";
import MsTablePagination from "../../../../common/pagination/TablePagination";
import MsTag from "../../../../common/components/MsTag";
import MsBottomContainer from "../../../definition/components/BottomContainer";
import ShowMoreBtn from "../../../../track/case/components/ShowMoreBtn";
import MsBatchEdit from "../../../definition/components/basis/BatchEdit";
import {API_METHOD_COLOUR, CASE_PRIORITY} from "../../../definition/model/JsonData";
import ApiListContainer from "../../../definition/components/list/ApiListContainer";
import PriorityTableItem from "../../../../track/common/tableItems/planview/PriorityTableItem";
import MsEnvironmentSelect from "../../../definition/components/case/MsEnvironmentSelect";
import TableSelectCountBar from "./TableSelectCountBar";
import {_filter, _sort, buildBatchParam, getLabel,} from "@/common/js/tableUtils";
import {getCurrentProjectID} from "@/common/js/utils";
import MsTableAdvSearchBar from "@/business/components/common/components/search/MsTableAdvSearchBar";
import {buildBatchParam} from "@/common/js/tableUtils";
import {
TEST_PLAN_RELEVANCE_API_DEFINITION_CONFIGS,
} from "@/business/components/common/components/search/search-components";
import ApiTableList from "@/business/components/api/definition/components/complete/ApiTableList";
export default {
name: "RelevanceApiList",
components: {
ApiTableList,
TableSelectCountBar,
MsEnvironmentSelect,
PriorityTableItem,
ApiListContainer,
MsTableOperatorButton,
MsTableOperator,
MsTablePagination,
MsTag,
MsBottomContainer,
ShowMoreBtn,
MsBatchEdit,
MsTable,
MsTableColumn,
MsTableAdvSearchBar
},
data() {
return {
condition: {
components: TEST_PLAN_RELEVANCE_API_DEFINITION_CONFIGS
},
selectCase: {},
result: {},
moduleId: "",
deletePath: "/test/case/delete",
screenHeight: 'calc(100vh - 400px)',//,
typeArr: [
{id: 'priority', name: this.$t('test_track.case.priority')},
],
priorityFilters: [
{text: 'P0', value: 'P0'},
{text: 'P1', value: 'P1'},
{text: 'P2', value: 'P2'},
{text: 'P3', value: 'P3'}
],
valueArr: {
priority: CASE_PRIORITY,
},
methodColorMap: new Map(API_METHOD_COLOUR),
tableData: [],
currentPage: 1,
pageSize: 10,
total: 0,
environmentId: "",
methodFilters: [
{text: 'GET', value: 'GET'},
{text: 'POST', value: 'POST'},
{text: 'PUT', value: 'PUT'},
{text: 'PATCH', value: 'PATCH'},
{text: 'DELETE', value: 'DELETE'},
{text: 'OPTIONS', value: 'OPTIONS'},
{text: 'HEAD', value: 'HEAD'},
{text: 'CONNECT', value: 'CONNECT'},
{text: 'DUBBO', value: 'DUBBO'},
{text: 'dubbo://', value: 'dubbo://'},
{text: 'SQL', value: 'SQL'},
{text: 'TCP', value: 'TCP'},
],
userFilters: [],
selectRows: new Set()
}
},
props: {
@ -210,14 +85,10 @@
},
projectId: String,
planId: String,
isTestPlan: Boolean
isTestPlan: Boolean,
},
created: function () {
if (this.$refs.apitable) {
this.$refs.apitable.clearSelectRows();
}
this.initTable();
this.getMaintainerOptions();
},
watch: {
selectNodeIds() {
@ -230,23 +101,10 @@
this.initTable();
}
},
computed: {
selectRows() {
if (this.$refs.apitable) {
return this.$refs.apitable.getSelectRows();
} else {
return new Set();
}
},
getApiRequestTypeName(){
if(this.currentProtocol === 'TCP'){
return this.$t('api_test.definition.api_agreement');
}else{
return this.$t('api_test.definition.api_type');
}
}
},
methods: {
setSelectRow(setSelectRow) {
this.selectRows = setSelectRow;
},
isApiListEnableChange(data) {
this.$emit('isApiListEnableChange', data);
},
@ -274,6 +132,7 @@
url = '/api/definition/list/relevance/';
this.condition.planId = this.planId;
}
this.result = this.$post(url + this.currentPage + "/" + this.pageSize, this.condition, response => {
this.total = response.data.itemCount;
this.tableData = response.data.listObject;
@ -282,93 +141,13 @@
item.tags = JSON.parse(item.tags);
}
});
this.genProtocalFilter(this.condition.protocol);
});
},
showExecResult(row) {
this.visible = false;
this.$emit('showExecResult', row);
},
filter(filters) {
_filter(filters, this.condition);
this.initTable();
},
sort(column) {
//
if (this.condition.orders) {
this.condition.orders = [];
}
_sort(column, this.condition);
this.initTable();
},
buildPagePath(path) {
return path + "/" + this.currentPage + "/" + this.pageSize;
},
getColor(flag, method) {
return this.methodColorMap.get(method);
},
setEnvironment(data) {
this.environmentId = data.id;
},
clearSelection() {
if (this.$refs.apitable) {
this.$refs.apitable.clearSelectRows();
this.$refs.apitable.clearSelection();
}
},
genProtocalFilter(protocalType) {
if (protocalType === "HTTP") {
this.methodFilters = [
{text: 'GET', value: 'GET'},
{text: 'POST', value: 'POST'},
{text: 'PUT', value: 'PUT'},
{text: 'PATCH', value: 'PATCH'},
{text: 'DELETE', value: 'DELETE'},
{text: 'OPTIONS', value: 'OPTIONS'},
{text: 'HEAD', value: 'HEAD'},
{text: 'CONNECT', value: 'CONNECT'},
];
} else if (protocalType === "TCP") {
this.methodFilters = [
{text: 'TCP', value: 'TCP'},
];
} else if (protocalType === "SQL") {
this.methodFilters = [
{text: 'SQL', value: 'SQL'},
];
} else if (protocalType === "DUBBO") {
this.methodFilters = [
{text: 'DUBBO', value: 'DUBBO'},
{text: 'dubbo://', value: 'dubbo://'},
];
} else {
this.methodFilters = [
{text: 'GET', value: 'GET'},
{text: 'POST', value: 'POST'},
{text: 'PUT', value: 'PUT'},
{text: 'PATCH', value: 'PATCH'},
{text: 'DELETE', value: 'DELETE'},
{text: 'OPTIONS', value: 'OPTIONS'},
{text: 'HEAD', value: 'HEAD'},
{text: 'CONNECT', value: 'CONNECT'},
{text: 'DUBBO', value: 'DUBBO'},
{text: 'dubbo://', value: 'dubbo://'},
{text: 'SQL', value: 'SQL'},
{text: 'TCP', value: 'TCP'},
];
}
},
getMaintainerOptions() {
this.$post('/user/project/member/tester/list', {projectId: getCurrentProjectID()},response => {
this.valueArr.userId = response.data;
this.userFilters = response.data.map(u => {
return {text: u.name, value: u.id};
});
});
},
getConditions() {
let sampleSelectRows = this.$refs.apitable.getSelectRows();
let sampleSelectRows = this.selectRows;
let param = buildBatchParam(this);
param.ids = Array.from(sampleSelectRows).map(row => row.id);
return param;
@ -390,31 +169,7 @@
</script>
<style scoped>
.operate-button > div {
display: inline-block;
margin-left: 10px;
}
.request-method {
padding: 0 5px;
color: #1E90FF;
}
.api-el-tag {
color: white;
}
.search-input {
float: right;
width: 30%;
margin-bottom: 20px;
margin-right: 20px;
}
.adv-search-bar {
float: right;
margin-top: 5px;
margin-right: 10px;
}
.card-content >>> .el-button-group {
float: left;
}
</style>

View File

@ -494,6 +494,7 @@ export default {
url: "",
protocol: this.currentProtocol,
environmentId: "",
remark: "",
moduleId: 'default-module',
modulePath: "/" + this.$t("commons.module_title")
};

View File

@ -0,0 +1,42 @@
export function getProtocolFilter(protocolType) {
if (protocolType === "HTTP") {
return [
{text: 'GET', value: 'GET'},
{text: 'POST', value: 'POST'},
{text: 'PUT', value: 'PUT'},
{text: 'PATCH', value: 'PATCH'},
{text: 'DELETE', value: 'DELETE'},
{text: 'OPTIONS', value: 'OPTIONS'},
{text: 'HEAD', value: 'HEAD'},
{text: 'CONNECT', value: 'CONNECT'},
];
} else if (protocolType === "TCP") {
return [
{text: 'TCP', value: 'TCP'},
];
} else if (protocolType === "SQL") {
return [
{text: 'SQL', value: 'SQL'},
];
} else if (protocolType === "DUBBO") {
return [
{text: 'DUBBO', value: 'DUBBO'},
{text: 'dubbo://', value: 'dubbo://'},
];
} else {
return [
{text: 'GET', value: 'GET'},
{text: 'POST', value: 'POST'},
{text: 'PUT', value: 'PUT'},
{text: 'PATCH', value: 'PATCH'},
{text: 'DELETE', value: 'DELETE'},
{text: 'OPTIONS', value: 'OPTIONS'},
{text: 'HEAD', value: 'HEAD'},
{text: 'CONNECT', value: 'CONNECT'},
{text: 'DUBBO', value: 'DUBBO'},
{text: 'dubbo://', value: 'dubbo://'},
{text: 'SQL', value: 'SQL'},
{text: 'TCP', value: 'TCP'},
];
}
}

View File

@ -0,0 +1,46 @@
<template>
<div class="info-container">
<slot></slot>
</div>
</template>
<script>
export default {
name: "ApiInfoContainer"
}
</script>
<style scoped>
.info-container {
border:1px #DCDFE6 solid; height: 100%;border-radius: 4px ;width: 100%
}
.info-container .icon {
padding: 5px;
}
.info-container .collapse {
cursor: pointer;
}
.info-container .collapse:hover {
opacity: 0.8;
}
.info-container .icon.is-active {
transform: rotate(90deg);
}
.info-container .pane {
background-color: white;
padding: 1px 0;
height: 250px;
overflow-y: auto;
}
.info-container .pane.cookie {
padding: 0;
}
</style>

View File

@ -0,0 +1,51 @@
<template>
<div>
<ms-form-divider :title="'其他信息'"/>
<api-info-container>
<el-form :model="api" ref="api-form" label-width="100px">
<el-collapse-transition>
<el-tabs v-model="activeName" style="margin: 20px">
<el-tab-pane :label="'备注'" name="remark" class="pane">
<form-rich-text-item class="remark-item" :disabled="readOnly" :data="api" prop="remark" label-width="0"/>
</el-tab-pane>
<el-tab-pane :label="'依赖关系'" name="dependencies" class="pane">
<dependencies-list :resource-id="api.id" resource-type="API" ref="dependencies"/>
</el-tab-pane>
</el-tabs>
</el-collapse-transition>
</el-form>
</api-info-container>
</div>
</template>
<script>
import MsFormDivider from "@/business/components/common/components/MsFormDivider";
import ApiInfoContainer from "@/business/components/api/definition/components/complete/ApiInfoContainer";
import DependenciesList from "@/business/components/common/components/graph/DependenciesList";
import FormRichTextItem from "@/business/components/track/case/components/FormRichTextItem";
export default {
name: "ApiOtherInfo",
components: {FormRichTextItem, DependenciesList, ApiInfoContainer, MsFormDivider},
props: ['api','readOnly'],
data() {
return {
activeName: 'remark'
}
},
watch: {
activeName() {
if (this.activeName === 'dependencies') {
this.$refs.dependencies.open();
}
}
},
}
</script>
<style scoped>
.remark-item {
padding: 15px 15px;
}
</style>

View File

@ -0,0 +1,90 @@
<template>
<div>
<ms-table
v-loading="result.loading"
:show-select-all="false"
:data="data"
:operators="operators"
:enable-selection="false"
ref="table"
:screen-height="null"
@refresh="getTableData">
<ms-table-column
prop="targetNum"
:label="$t('commons.id')"
min-width="80"/>
<ms-table-column
prop="targetName"
:label="$t('commons.name')"
min-width="120"/>
<ms-table-column
prop="creator"
:label="$t('commons.create_user')"
min-width="120">
</ms-table-column>
</ms-table>
<api-relationship-relevance
:api-definition-id="apiDefinitionId"
@refresh="getTableData"
:relationship-type="relationshipType"
ref="testCaseRelevance"/>
</div>
</template>
<script>
import MsTable from "@/business/components/common/components/table/MsTable";
import MsTableColumn from "@/business/components/common/components/table/MsTableColumn";
import MsTableSearchBar from "@/business/components/common/components/MsTableSearchBar";
import RelationshipFunctionalRelevance
from "@/business/components/track/case/components/RelationshipFunctionalRelevance";
import {getRelationshipApi} from "@/network/api";
import ApiRelationshipRelevance
from "@/business/components/api/definition/components/complete/ApiRelationshipRelevance";
export default {
name: "ApiRelationshipList",
components: {ApiRelationshipRelevance, RelationshipFunctionalRelevance, MsTableSearchBar, MsTableColumn, MsTable},
data() {
return {
result: {},
data: [],
operators: [
{
tip: this.$t('commons.delete'), icon: "el-icon-delete", type: "danger",
exec: this.handleDelete,
// permissions: ['PROJECT_TRACK_CASE:READ+DELETE']
}
],
condition: {},
options: [],
value: ''
}
},
props: {
apiDefinitionId: String,
readOnly: Boolean,
relationshipType: String,
},
methods: {
getTableData() {
getRelationshipApi(this.apiDefinitionId, this.relationshipType, (data) => {
this.data = data;
});
},
openRelevance() {
this.$refs.testCaseRelevance.open();
},
handleDelete(item) {
this.$emit('deleteRelationship', item.sourceId, item.targetId);
},
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,142 @@
<template>
<test-case-relevance-base
@setProject="setProject"
@save="saveCaseRelevance"
:multiple-project="false"
ref="baseRelevance">
<template v-slot:aside>
<ms-api-module
:relevance-project-id="projectId"
@nodeSelectEvent="nodeChange"
@protocolChange="handleProtocolChange"
@refreshTable="initTable"
:is-read-only="true"
ref="nodeTree"/>
</template>
<api-table-list
:table-data="tableData"
:condition="condition"
:select-node-ids="selectNodeIds"
:result="result"
:current-protocol="currentProtocol"
:current-page="currentPage"
:page-size="pageSize"
@refreshTable="initTable"
ref="apitable"/>
</test-case-relevance-base>
</template>
<script>
import MsApiModule from "@/business/components/api/definition/components/module/ApiModule";
import TestCaseRelevanceBase from "@/business/components/track/plan/view/comonents/base/TestCaseRelevanceBase";
import ApiTableList from "@/business/components/api/definition/components/complete/ApiTableList";
export default {
name: "ApiRelationshipRelevance",
components: {
ApiTableList,
TestCaseRelevanceBase,
MsApiModule,
},
props: ['apiDefinitionId', 'relationshipType'],
data() {
return {
showCasePage: true,
currentProtocol: null,
currentModule: null,
selectNodeIds: [],
condition: {},
currentRow: {},
projectId: "",
result: {},
currentPage: 1,
pageSize: 10,
tableData: []
};
},
watch: {
projectId() {
this.initTable();
},
selectNodeIds() {
this.initTable();
},
currentProtocol() {
this.$refs.nodeTree.list();
this.initTable();
}
},
methods: {
open() {
this.$refs.baseRelevance.open();
this.initTable();
if (this.$refs.nodeTree) {
this.$refs.nodeTree.list();
}
},
initTable() {
this.condition.filters = {status: ["Prepare", "Underway", "Completed"]};
this.condition.moduleIds = this.selectNodeIds;
this.condition.projectId = this.projectId;
if (this.currentProtocol != null) {
this.condition.protocol = this.currentProtocol;
} else {
this.condition.protocol = "HTTP";
}
if (this.apiDefinitionId) {
this.condition.id = this.apiDefinitionId;
this.result = this.$post(this.buildPagePath('/api/definition/relationship/relate'), this.condition, response => {
this.total = response.data.itemCount;
this.tableData = response.data.listObject;
this.tableData.forEach(item => {
if (item.tags && item.tags.length > 0) {
item.tags = JSON.parse(item.tags);
}
});
});
}
},
buildPagePath(path) {
return path + "/" + this.currentPage + "/" + this.pageSize;
},
setProject(projectId) {
this.projectId = projectId;
},
nodeChange(node, nodeIds, pNodes) {
this.selectNodeIds = nodeIds;
},
handleProtocolChange(protocol) {
this.currentProtocol = protocol;
},
saveCaseRelevance() {
let param = {};
param.ids = this.$refs.apitable.getSelectIds();
param.request = this.condition;
if (this.relationshipType === 'PRE') {
param.targetIds = param.ids;
} else {
param.sourceIds = param.ids;
}
param.id = this.apiDefinitionId;
param.type = 'API';
this.result = this.$post('/relationship/edge/save/batch', param, () => {
this.$success(this.$t('commons.save_success'));
this.$refs.baseRelevance.close();
this.$emit('refresh');
});
},
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,243 @@
<template>
<div>
<slot name="header"></slot>
<el-input :placeholder="$t('commons.search_by_name_or_id')" @blur="initTable" class="search-input" size="small"
@keyup.enter.native="initTable" v-model="condition.name"/>
<ms-table-adv-search-bar :condition.sync="condition" class="adv-search-bar"
v-if="condition.components !== undefined && condition.components.length > 0"
@search="initTable"/>
<ms-table :data="tableData" :select-node-ids="selectNodeIds" :condition="condition" :page-size="pageSize"
:total="total" enableSelection
:screenHeight="screenHeight"
operator-width="170px"
@refresh="initTable"
ref="apitable">
<ms-table-column
prop="num"
label="ID"
min-width="80px"
sortable>
</ms-table-column>
<ms-table-column
prop="name"
:label="$t('api_test.definition.api_name')"
sortable
width="120px"/>
<ms-table-column
prop="method"
sortable="custom"
column-key="method"
:filters="methodFilters"
:label="getApiRequestTypeName"
width="120px">
<template v-slot:default="scope">
<el-tag size="mini"
:style="{'background-color': getColor(true, scope.row.method), border: getColor(true, scope.row.method)}"
class="api-el-tag">
{{ scope.row.method }}
</el-tag>
</template>
</ms-table-column>
<ms-table-column
prop="userName"
sortable="custom"
:filters="userFilters"
column-key="user_id"
:label="$t('api_test.definition.api_principal')"
width="100px"/>
<ms-table-column
prop="path"
width="120px"
:label="$t('api_test.definition.api_path')"/>
<ms-table-column
prop="tags"
:label="$t('commons.tag')"
width="120px">
<template v-slot:default="scope">
<ms-tag v-for="(itemName,index) in scope.row.tags" :key="index" type="success" effect="plain"
:show-tooltip="true" :content="itemName"
style="margin-left: 0px; margin-right: 2px"/>
</template>
</ms-table-column>
<ms-table-column
width="160"
:label="$t('api_test.definition.api_last_time')"
sortable="custom"
prop="updateTime">
<template v-slot:default="scope">
<span>{{ scope.row.updateTime | timestampFormatDate }}</span>
</template>
</ms-table-column>
<ms-table-column
prop="caseTotal"
width="80px"
:label="$t('api_test.definition.api_case_number')"/>
</ms-table>
<ms-table-pagination :change="initTable" :current-page.sync="currentPage" :page-size.sync="pageSize"
:total="total"/>
</div>
</template>
<script>
import MsTable from "@/business/components/common/components/table/MsTable";
import MsTableColumn from "@/business/components/common/components/table/MsTableColumn";
import MsTableOperator from "../../../../common/components/MsTableOperator";
import MsTableOperatorButton from "../../../../common/components/MsTableOperatorButton";
import MsTablePagination from "../../../../common/pagination/TablePagination";
import MsTag from "../../../../common/components/MsTag";
import MsBottomContainer from "../../../definition/components/BottomContainer";
import ShowMoreBtn from "../../../../track/case/components/ShowMoreBtn";
import MsBatchEdit from "../../../definition/components/basis/BatchEdit";
import {API_METHOD_COLOUR} from "../../../definition/model/JsonData";
import ApiListContainer from "../../../definition/components/list/ApiListContainer";
import PriorityTableItem from "../../../../track/common/tableItems/planview/PriorityTableItem";
import MsEnvironmentSelect from "../../../definition/components/case/MsEnvironmentSelect";
import MsTableAdvSearchBar from "@/business/components/common/components/search/MsTableAdvSearchBar";
import {getProtocolFilter} from "@/business/components/api/definition/api-definition";
import {getProjectMember} from "@/network/user";
import TableSelectCountBar from "@/business/components/api/automation/scenario/api/TableSelectCountBar";
export default {
name: "ApiTableList",
components: {
TableSelectCountBar,
MsEnvironmentSelect,
PriorityTableItem,
ApiListContainer,
MsTableOperatorButton,
MsTableOperator,
MsTablePagination,
MsTag,
MsBottomContainer,
ShowMoreBtn,
MsBatchEdit,
MsTable,
MsTableColumn,
MsTableAdvSearchBar
},
data() {
return {
moduleId: "",
deletePath: "/test/case/delete",
typeArr: [
{id: 'priority', name: this.$t('test_track.case.priority')},
],
priorityFilters: [
{text: 'P0', value: 'P0'},
{text: 'P1', value: 'P1'},
{text: 'P2', value: 'P2'},
{text: 'P3', value: 'P3'}
],
methodColorMap: new Map(API_METHOD_COLOUR),
methodFilters: [],
userFilters: []
}
},
props: {
currentProtocol: String,
selectNodeIds: Array,
result: Object,
tableData: Array,
condition: Object,
currentPage: Number,
pageSize: Number,
screenHeight: {
type: [Number, String],
default() {
return 'calc(100vh - 400px)';
}
}
},
created: function () {
getProjectMember((data) => {
this.userFilters = data;
});
this.getProtocolFilter();
},
watch: {
currentProtocol() {
this.getProtocolFilter();
}
},
mounted() {
if (this.$refs.apitable) {
this.$emit('setSelectRow', this.$refs.apitable.getSelectRows());
} else {
this.$emit('setSelectRow', new Set());
}
},
computed: {
getApiRequestTypeName(){
if(this.currentProtocol === 'TCP'){
return this.$t('api_test.definition.api_agreement');
}else{
return this.$t('api_test.definition.api_type');
}
},
total() {
return this.tableData ? this.tableData.length : 0;
},
},
methods: {
buildPagePath(path) {
return path + "/" + this.currentPage + "/" + this.pageSize;
},
getColor(flag, method) {
return this.methodColorMap.get(method);
},
getProtocolFilter() {
this.methodFilters = getProtocolFilter(this.currentProtocol);
},
getSelectIds() {
return this.$refs.apitable.selectIds;
},
initTable() {
this.$emit('refreshTable');
},
clear() {
if (this.$refs.apitable) {
this.$refs.apitable.clear();
}
},
},
}
</script>
<style scoped>
.request-method {
padding: 0 5px;
color: #1E90FF;
}
.api-el-tag {
color: white;
}
.search-input {
float: right;
width: 30%;
margin-bottom: 20px;
margin-right: 20px;
}
.adv-search-bar {
float: right;
margin-top: 5px;
margin-right: 10px;
}
</style>

View File

@ -11,7 +11,8 @@
<el-button type="primary" size="small" @click="saveApi" title="ctrl + s" v-permission="['PROJECT_API_DEFINITION:READ+EDIT_API']">{{ $t('commons.save') }}</el-button>
</div>
<br/>
<p class="tip">{{ $t('test_track.plan_view.base_info') }} </p>
<ms-form-divider :title="$t('test_track.plan_view.base_info')"/>
<!-- 基础信息 -->
<div class="base-info">
@ -96,7 +97,7 @@
</div>
<!-- MOCK信息 -->
<p class="tip">{{ $t('test_track.plan_view.mock_info') }} </p>
<ms-form-divider :title="$t('test_track.plan_view.mock_info')"/>
<div class="base-info mock-info">
<el-row>
<el-col :span="20">
@ -114,7 +115,7 @@
<!-- 请求参数 -->
<div>
<p class="tip">{{ $t('api_test.definition.request.req_param') }} </p>
<ms-form-divider :title="$t('api_test.definition.request.req_param')"/>
<ms-api-request-form :showScript="false" :request="request" :headers="request.headers"
:isShowEnable="isShowEnable"/>
</div>
@ -122,8 +123,10 @@
</el-form>
<!-- 响应内容-->
<p class="tip">{{ $t('api_test.definition.request.res_param') }} </p>
<ms-response-text :response="response"></ms-response-text>
<ms-form-divider :title="$t('api_test.definition.request.res_param')"/>
<ms-response-text :response="response"/>
<api-other-info :api="httpForm"/>
<ms-change-history ref="changeHistory"/>
@ -142,10 +145,15 @@
import MsSelectTree from "../../../../common/select-tree/SelectTree";
import MsChangeHistory from "../../../../history/ChangeHistory";
import {getCurrentProjectID, getUUID} from "@/common/js/utils";
import MsFormDivider from "@/business/components/common/components/MsFormDivider";
import ApiOtherInfo from "@/business/components/api/definition/components/complete/ApiOtherInfo";
export default {
name: "MsAddCompleteHttpApi",
components: {MsJsr233Processor, MsResponseText, MsApiRequestForm, MsInputTag, MsSelectTree,MsChangeHistory},
components: {
ApiOtherInfo,
MsFormDivider,
MsJsr233Processor, MsResponseText, MsApiRequestForm, MsInputTag, MsSelectTree,MsChangeHistory},
data() {
let validateURL = (rule, value, callback) => {
if (!this.httpForm.path.startsWith("/") || this.httpForm.path.match(/\s/) != null) {

View File

@ -0,0 +1,100 @@
<template>
<api-table-list
:table-data="tableData"
:condition="condition"
:select-node-ids="selectNodeIds"
:result="result"
:current-protocol="currentProtocol"
:current-page="currentPage"
:page-size="pageSize"
@refreshTable="initTable"
ref="apitable"/>
</template>
<script>
import {
TEST_PLAN_RELEVANCE_API_DEFINITION_CONFIGS,
} from "@/business/components/common/components/search/search-components";
import ApiTableList from "@/business/components/api/definition/components/complete/ApiTableList";
export default {
name: "RelationshipRelevanceList",
components: {
ApiTableList,
},
data() {
return {
condition: {
components: TEST_PLAN_RELEVANCE_API_DEFINITION_CONFIGS
},
result: {},
tableData: [],
currentPage: 1,
pageSize: 10,
environmentId: "",
selectRows: new Set()
}
},
props: {
currentProtocol: String,
selectNodeIds: Array,
visible: {
type: Boolean,
default: false,
},
projectId: String,
apiDefinitionId: String
},
created: function () {
this.initTable();
},
watch: {
selectNodeIds() {
this.initTable();
},
currentProtocol() {
this.initTable();
},
projectId() {
this.initTable();
}
},
methods: {
initTable(projectId) {
this.condition.filters = {status: ["Prepare", "Underway", "Completed"]};
this.condition.moduleIds = this.selectNodeIds;
if (projectId != null && typeof projectId === 'string') {
this.condition.projectId = projectId;
} else if (this.projectId != null) {
this.condition.projectId = this.projectId;
}
if (this.currentProtocol != null) {
this.condition.protocol = this.currentProtocol;
} else {
this.condition.protocol = "HTTP";
}
if (this.apiDefinitionId) {
this.condition.id = this.apiDefinitionId;
this.result = this.$post(this.buildPagePath('/api/definition/relationship/relate'), this.condition, response => {
this.total = response.data.itemCount;
this.tableData = response.data.listObject;
this.tableData.forEach(item => {
if (item.tags && item.tags.length > 0) {
item.tags = JSON.parse(item.tags);
}
});
});
}
},
buildPagePath(path) {
return path + "/" + this.currentPage + "/" + this.pageSize;
},
},
}
</script>
<style scoped>
</style>

View File

@ -180,7 +180,10 @@
<ms-batch-edit ref="batchEdit" @batchEdit="batchEdit" :data-count="$refs.table ? $refs.table.selectDataCounts : 0" :typeArr="typeArr" :value-arr="valueArr"/>
<!--高级搜索-->
<ms-table-adv-search-bar :condition.sync="condition" :showLink="false" ref="searchBar" @search="search"/>
<case-batch-move @refresh="initTable" @moveSave="moveSave" ref="testCaseBatchMove"></case-batch-move>
<case-batch-move @refresh="initTable" @moveSave="moveSave" ref="testCaseBatchMove"/>
<relationship-graph-drawer :graph-data="graphData" ref="relationshipGraph"/>
</div>
</template>
@ -215,11 +218,15 @@ import {
import HeaderLabelOperate from "@/business/components/common/head/HeaderLabelOperate";
import {Body} from "@/business/components/api/definition/model/ApiTestModel";
import {editApiDefinitionOrder} from "@/network/api";
import {getProtocolFilter} from "@/business/components/api/definition/api-definition";
import {getGraphByCondition} from "@/network/graph";
import RelationshipGraphDrawer from "@/business/components/xpack/graph/RelationshipGraphDrawer";
export default {
name: "ApiList",
components: {
RelationshipGraphDrawer,
HeaderLabelOperate,
CaseBatchMove,
ApiStatus,
@ -253,6 +260,7 @@ export default {
moduleId: "",
enableOrderDrag: true,
selectDataRange: "all",
graphData: [],
deletePath: "/test/case/delete",
buttons: [
{
@ -269,6 +277,12 @@ export default {
name: this.$t('api_test.definition.request.batch_move'),
handleClick: this.handleBatchMove,
permissions: ['PROJECT_API_DEFINITION:READ+EDIT_API']
},
{
name: this.$t('生成依赖关系'),
isXPack: true,
handleClick: this.generateGraph,
permissions: ['PROJECT_API_DEFINITION:READ+EDIT_API']
}
],
trashButtons: [
@ -488,6 +502,12 @@ export default {
}
});
},
generateGraph() {
getGraphByCondition('API', buildBatchParam(this, this.$refs.table.selectIds),(data) => {
this.graphData = data;
this.$refs.relationshipGraph.open();
});
},
handleBatchMove() {
this.$refs.testCaseBatchMove.open(this.moduleTree, [], this.moduleOptions);
},
@ -543,7 +563,7 @@ export default {
}
if (this.condition.projectId) {
this.result = this.$post("/api/definition/list/" + this.currentPage + "/" + this.pageSize, this.condition, response => {
this.genProtocalFilter(this.condition.protocol);
getProtocolFilter(this.condition.protocol);
this.total = response.data.itemCount;
this.tableData = response.data.listObject;
this.tableData.forEach(item => {
@ -557,48 +577,6 @@ export default {
this.$emit("refreshTree");
}
},
genProtocalFilter(protocalType) {
if (protocalType === "HTTP") {
this.methodFilters = [
{text: 'GET', value: 'GET'},
{text: 'POST', value: 'POST'},
{text: 'PUT', value: 'PUT'},
{text: 'PATCH', value: 'PATCH'},
{text: 'DELETE', value: 'DELETE'},
{text: 'OPTIONS', value: 'OPTIONS'},
{text: 'HEAD', value: 'HEAD'},
{text: 'CONNECT', value: 'CONNECT'},
];
} else if (protocalType === "TCP") {
this.methodFilters = [
{text: 'TCP', value: 'TCP'},
];
} else if (protocalType === "SQL") {
this.methodFilters = [
{text: 'SQL', value: 'SQL'},
];
} else if (protocalType === "DUBBO") {
this.methodFilters = [
{text: 'DUBBO', value: 'DUBBO'},
{text: 'dubbo://', value: 'dubbo://'},
];
} else {
this.methodFilters = [
{text: 'GET', value: 'GET'},
{text: 'POST', value: 'POST'},
{text: 'PUT', value: 'PUT'},
{text: 'PATCH', value: 'PATCH'},
{text: 'DELETE', value: 'DELETE'},
{text: 'OPTIONS', value: 'OPTIONS'},
{text: 'HEAD', value: 'HEAD'},
{text: 'CONNECT', value: 'CONNECT'},
{text: 'DUBBO', value: 'DUBBO'},
{text: 'dubbo://', value: 'dubbo://'},
{text: 'SQL', value: 'SQL'},
{text: 'TCP', value: 'TCP'},
];
}
},
getMaintainerOptions() {
this.$post('/user/project/member/tester/list', {projectId: getCurrentProjectID()}, response => {
this.valueArr.userId = response.data;

View File

@ -1,11 +1,11 @@
<template>
<div class="dependencies-container">
<el-main>
<el-main v-xpack>
<i class="el-icon-view" @click="openGraph"></i>
</el-main>
<test-case-relationship-list :title="'前置对象'" relationship-type="PRE" :case-id="caseId" ref="preRelationshipList"/>
<test-case-relationship-list :title="'后置对象'" relationship-type="POST" :case-id="caseId" ref="postRelationshipList"/>
<relationship-list :title="'前置对象'" relationship-type="PRE" :resource-id="resourceId" :resource-type="resourceType" ref="preRelationshipList"/>
<relationship-list :title="'后置对象'" relationship-type="POST" :resource-id="resourceId" :resource-type="resourceType" ref="postRelationshipList"/>
<relationship-graph-drawer :graph-data="graphData" ref="relationshipGraph"/>
@ -13,14 +13,15 @@
</template>
<script>
import TestCaseRelationshipList from "@/business/components/track/case/components/TestCaseRelationshipList";
import RelationshipGraphDrawer from "@/business/components/xpack/graph/RelationshipGraphDrawer";
import {getRelationshipGraph} from "@/network/graph";
import RelationshipList from "@/business/components/common/components/graph/RelationshipList";
export default {
name: "TestCaseDependencies",
components: {RelationshipGraphDrawer, TestCaseRelationshipList},
name: "DependenciesList",
components: {RelationshipList, RelationshipGraphDrawer},
props: [
'caseId'
'resourceId',
'resourceType'
],
data() {
return {
@ -33,7 +34,7 @@ export default {
this.$refs.postRelationshipList.getTableData();
},
openGraph() {
getRelationshipGraph(this.caseId, 'TEST_CASE', (data) => {
getRelationshipGraph(this.resourceId, this.resourceType, (data) => {
this.graphData = data;
this.$refs.relationshipGraph.open();
});

View File

@ -0,0 +1,75 @@
<template>
<el-main>
<span>{{ title }}</span>
<el-button class="add-btn"
:disabled="readOnly" type="primary" size="mini" @click="openRelevance">{{ $t('添加') }}</el-button>
<test-case-relationship-list
v-if="resourceType === 'TEST_CASE'"
:case-id="resourceId"
:relationship-type="relationshipType"
@deleteRelationship="handleDelete"
ref="testCaseRelationshipList"/>
<api-relationship-list
v-if="resourceType === 'API'"
:api-definition-id="resourceId"
:relationship-type="relationshipType"
@deleteRelationship="handleDelete"
ref="testCaseRelationshipList"/>
</el-main>
</template>
<script>
import MsTable from "@/business/components/common/components/table/MsTable";
import MsTableColumn from "@/business/components/common/components/table/MsTableColumn";
import MsTableSearchBar from "@/business/components/common/components/MsTableSearchBar";
import RelationshipFunctionalRelevance
from "@/business/components/track/case/components/RelationshipFunctionalRelevance";
import {deleteRelationshipEdge} from "@/network/relationship-edge";
import TestCaseRelationshipList from "@/business/components/track/case/components/TestCaseRelationshipList";
import ApiRelationshipList from "@/business/components/api/definition/components/complete/ApiRelationshipList";
export default {
name: "RelationshipList",
components: {
ApiRelationshipList,
TestCaseRelationshipList, RelationshipFunctionalRelevance, MsTableSearchBar, MsTableColumn, MsTable},
data() {
return {
result: {},
data: [],
condition: {},
options: [],
value: ''
}
},
props: {
resourceId: String,
readOnly: Boolean,
relationshipType: String,
title: String,
resourceType: String
},
methods: {
getTableData() {
this.$refs.testCaseRelationshipList.getTableData();
},
openRelevance() {
this.$refs.testCaseRelationshipList.openRelevance();
},
handleDelete(sourceId, targetId) {
deleteRelationshipEdge(sourceId, targetId, () => {
this.getTableData();
this.$success(this.$t('commons.delete_success'));
});
},
}
}
</script>
<style scoped>
.add-btn {
margin-left: 20px;
}
</style>

View File

@ -8,7 +8,6 @@
@setTreeNodes="setTreeNodes"
@exportTestCase="exportTestCase"
@saveAsEdit="editTestCase"
@openGraph="openGraph"
:show-operator="true"
@createCase="handleCaseSimpleCreate($event, 'add')"
@refreshAll="refreshAll"
@ -324,9 +323,6 @@ export default {
}
this.$refs.testCaseList.exportTestCase(type);
},
openGraph() {
this.$refs.testCaseList.generateGraph();
},
addListener() {
let index = this.tabs.findIndex(item => item.name === this.activeName); // tabindex
if (index != -1) { // tab

View File

@ -9,16 +9,18 @@
</div>
<el-dropdown-menu slot="dropdown" class="dropdown-menu-class">
<div class="show-more-btn-title">{{$t('test_track.case.batch_handle', [size])}}</div>
<el-dropdown-item v-for="(btn,index) in buttons" :disabled="isDisable(btn)" :key="index" @click.native.stop="click(btn)">
<span v-for="(btn,index) in buttons" :key="index">
<el-dropdown-item :disabled="isDisable(btn)" v-if="isXPack(btn)" @click.native.stop="click(btn)">
{{btn.name}}
</el-dropdown-item>
</span>
</el-dropdown-menu>
</el-dropdown>
</div>
</template>
<script>
import {hasPermissions} from "@/common/js/utils";
import {hasLicense, hasPermissions} from "@/common/js/utils";
export default {
name: "ShowMoreBtn",
@ -53,6 +55,13 @@
return !hasPermissions(...item.permissions);
}
return false;
},
isXPack(item) {
if (item.isXPack) {
return hasLicense();
} else {
return true;
}
}
}
}

View File

@ -40,7 +40,7 @@
</el-tab-pane>
<el-tab-pane :label="$t('依赖关系')" name="relationship">
<test-case-dependencies :case-id="caseId" ref="relationship"/>
<dependencies-list :resource-id="caseId" resource-type="TEST_CASE" ref="relationship"/>
</el-tab-pane>
<el-tab-pane :label="$t('test_track.case.attachment')" name="attachment">
@ -82,12 +82,12 @@ import TestCaseAttachment from "@/business/components/track/case/components/Test
import TestCaseIssueRelate from "@/business/components/track/case/components/TestCaseIssueRelate";
import FormRichTextItem from "@/business/components/track/case/components/FormRichTextItem";
import TestCaseTestRelate from "@/business/components/track/case/components/TestCaseTestRelate";
import TestCaseDependencies from "@/business/components/track/case/components/TestCaseDependencies";
import DependenciesList from "@/business/components/common/components/graph/DependenciesList";
export default {
name: "TestCaseEditOtherInfo",
components: {
TestCaseDependencies,
DependenciesList,
TestCaseTestRelate,
FormRichTextItem, TestCaseIssueRelate, TestCaseAttachment, MsRichText, TestCaseRichText},
props: ['form', 'labelWidth', 'caseId', 'readOnly', 'projectId', 'isTestPlan', 'planId'],

View File

@ -319,6 +319,12 @@ export default {
name: this.$t('test_track.case.batch_delete_case'),
handleClick: this.handleDeleteBatchToGc,
permissions: ['PROJECT_TRACK_CASE:READ+DELETE']
},
{
name: this.$t('生成依赖关系'),
isXPack: true,
handleClick: this.generateGraph,
permissions: ['PROJECT_API_DEFINITION:READ+EDIT_API']
}
],
trashButtons: [

View File

@ -1,9 +1,5 @@
<template>
<el-main>
<span>{{ title }}</span>
<el-button class="add-btn"
:disabled="readOnly" type="primary" size="mini" @click="openRelevance">{{ $t('添加') }}</el-button>
<div>
<ms-table
v-loading="result.loading"
:show-select-all="false"
@ -45,7 +41,7 @@
:relationship-type="relationshipType"
ref="testCaseRelevance"/>
</el-main>
</div>
</template>
<script>
@ -55,7 +51,6 @@ import MsTableSearchBar from "@/business/components/common/components/MsTableSea
import RelationshipFunctionalRelevance
from "@/business/components/track/case/components/RelationshipFunctionalRelevance";
import {getRelationshipCase} from "@/network/testCase";
import {deleteRelationshipEdge} from "@/network/relationship-edge";
export default {
name: "TestCaseRelationshipList",
components: {RelationshipFunctionalRelevance, MsTableSearchBar, MsTableColumn, MsTable},
@ -76,16 +71,9 @@ export default {
}
},
props: {
tip: {
type: String,
default() {
return this.$t('commons.search_by_name_or_id');
}
},
caseId: String,
readOnly: Boolean,
relationshipType: String,
title: String,
},
computed: {
isCustomNum() {
@ -102,17 +90,11 @@ export default {
this.$refs.testCaseRelevance.open();
},
handleDelete(item) {
deleteRelationshipEdge(item.sourceId, item.targetId, () => {
this.getTableData();
this.$success(this.$t('commons.delete_success'));
});
this.$emit('deleteRelationship', item.sourceId, item.targetId);
},
}
}
</script>
<style scoped>
.add-btn {
margin-left: 20px;
}
</style>

View File

@ -81,11 +81,6 @@ export default {
label: this.$t('api_test.export_config'),
callback: this.handleExport,
permissions: ['PROJECT_TRACK_CASE:READ+EXPORT']
},
{
label: this.$t('查看依赖'),
callback: this.openGraph,
// permissions: ['PROJECT_TRACK_CASE:READ+EXPORT']
}
]
};
@ -114,9 +109,6 @@ export default {
},
},
methods: {
openGraph() {
this.$emit('openGraph');
},
addTestCase(){
if (!this.projectId) {
this.$warning(this.$t('commons.check_project_tip'));

View File

@ -31,3 +31,7 @@ export function editApiDefinitionOrder(request, callback) {
export function editApiTestCaseOrder(request, callback) {
return basePost('/api/testcase/edit/order', request, callback);
}
export function getRelationshipApi(id, relationshipType, callback) {
return baseGet('/api/definition/relationship/' + id + '/' + relationshipType, callback);
}