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

This commit is contained in:
wenyann 2020-05-12 10:46:45 +08:00
commit d7ccef6ee5
34 changed files with 325 additions and 229 deletions

View File

@ -28,7 +28,7 @@
and test_case.name like CONCAT('%', #{request.name},'%') and test_case.name like CONCAT('%', #{request.name},'%')
</if> </if>
<if test="request.nodeIds != null and request.nodeIds.size() > 0"> <if test="request.nodeIds != null and request.nodeIds.size() > 0">
and test_plan_test_case.node_id in and test_case.node_id in
<foreach collection="request.nodeIds" item="nodeId" separator="," open="(" close=")"> <foreach collection="request.nodeIds" item="nodeId" separator="," open="(" close=")">
#{nodeId} #{nodeId}
</foreach> </foreach>

View File

@ -7,17 +7,20 @@ 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.controller.request.ReportRequest; import io.metersphere.controller.request.ReportRequest;
import io.metersphere.dto.LogDetailDTO;
import io.metersphere.dto.ReportDTO; import io.metersphere.dto.ReportDTO;
import io.metersphere.report.base.*; import io.metersphere.report.base.*;
import io.metersphere.service.ReportService; import io.metersphere.service.ReportService;
import io.metersphere.user.SessionUtils; import io.metersphere.user.SessionUtils;
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.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.List; import java.util.List;
import java.util.Map;
@RestController @RestController
@RequestMapping(value = "performance/report") @RequestMapping(value = "performance/report")
@ -97,7 +100,16 @@ public class PerformanceReportController {
} }
@GetMapping("log/{reportId}") @GetMapping("log/{reportId}")
public Map<String, String> stop(@PathVariable String reportId) { public List<LogDetailDTO> logs(@PathVariable String reportId) {
return reportService.log(reportId); return reportService.logs(reportId);
}
@GetMapping("log/download/{logId}")
public ResponseEntity<byte[]> downloadLog(@PathVariable String logId) {
byte[] bytes = reportService.downloadLog(logId);
return ResponseEntity.ok()
.contentType(MediaType.parseMediaType("application/octet-stream"))
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"jmeter.log\"")
.body(bytes);
} }
} }

View File

@ -53,7 +53,7 @@ public class WorkspaceController {
@PostMapping("special/update") @PostMapping("special/update")
@RequiresRoles(RoleConstants.ADMIN) @RequiresRoles(RoleConstants.ADMIN)
public void updateWorkspaceByAdmin(@RequestBody Workspace workspace) { public void updateWorkspaceByAdmin(@RequestBody Workspace workspace) {
workspaceService.updateWorkspacebyAdmin(workspace); workspaceService.updateWorkspaceByAdmin(workspace);
} }
@GetMapping("special/delete/{workspaceId}") @GetMapping("special/delete/{workspaceId}")

View File

@ -0,0 +1,12 @@
package io.metersphere.dto;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class LogDetailDTO {
private String id;
private String resourceName;
private String content;
}

View File

@ -109,12 +109,8 @@ public class OrganizationService {
} }
public void updateOrgMember(OrganizationMemberDTO memberDTO) { public void updateOrgMember(OrganizationMemberDTO memberDTO) {
User user = new User();
BeanUtils.copyProperties(memberDTO, user);
userMapper.updateByPrimaryKeySelective(user);
//
String orgId = memberDTO.getOrganizationId(); String orgId = memberDTO.getOrganizationId();
String userId = user.getId(); String userId = memberDTO.getId();
// 已有角色 // 已有角色
List<Role> memberRoles = extUserRoleMapper.getOrganizationMemberRoles(orgId, userId); List<Role> memberRoles = extUserRoleMapper.getOrganizationMemberRoles(orgId, userId);
// 修改后的角色 // 修改后的角色

View File

@ -13,6 +13,7 @@ import io.metersphere.commons.constants.ReportKeys;
import io.metersphere.commons.exception.MSException; import io.metersphere.commons.exception.MSException;
import io.metersphere.commons.utils.LogUtil; import io.metersphere.commons.utils.LogUtil;
import io.metersphere.controller.request.ReportRequest; import io.metersphere.controller.request.ReportRequest;
import io.metersphere.dto.LogDetailDTO;
import io.metersphere.dto.ReportDTO; import io.metersphere.dto.ReportDTO;
import io.metersphere.engine.Engine; import io.metersphere.engine.Engine;
import io.metersphere.engine.EngineFactory; import io.metersphere.engine.EngineFactory;
@ -22,6 +23,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -157,35 +159,45 @@ public class ReportService {
return extLoadTestReportMapper.selectByPrimaryKey(id); return extLoadTestReportMapper.selectByPrimaryKey(id);
} }
public Map<String, String> log(String reportId) { public List<LogDetailDTO> logs(String reportId) {
Map<String, String> logMap = new HashMap<>();
LoadTestReportLogExample example = new LoadTestReportLogExample(); LoadTestReportLogExample example = new LoadTestReportLogExample();
example.createCriteria().andReportIdEqualTo(reportId); example.createCriteria().andReportIdEqualTo(reportId);
List<LoadTestReportLog> loadTestReportLogs = loadTestReportLogMapper.selectByExampleWithBLOBs(example); List<LoadTestReportLog> loadTestReportLogs = loadTestReportLogMapper.selectByExampleWithBLOBs(example);
loadTestReportLogs.stream().map(log -> { return loadTestReportLogs.stream().map(log -> {
Map<String, String> result = new HashMap<>(); LogDetailDTO detailDTO = new LogDetailDTO();
detailDTO.setId(log.getId());
TestResource testResource = testResourceService.getTestResource(log.getResourceId()); TestResource testResource = testResourceService.getTestResource(log.getResourceId());
String content = log.getContent();
// 显示前 2048
content = StringUtils.substring(content, 0, 2048);
detailDTO.setContent(content);
if (testResource == null) { if (testResource == null) {
result.put(log.getResourceId(), log.getContent()); detailDTO.setResourceName(log.getResourceId());
return result; return detailDTO;
} }
String configuration = testResource.getConfiguration(); String configuration = testResource.getConfiguration();
if (StringUtils.isBlank(configuration)) {
detailDTO.setResourceName(log.getResourceId());
return detailDTO;
}
JSONObject object = JSON.parseObject(configuration); JSONObject object = JSON.parseObject(configuration);
if (StringUtils.isNotBlank(object.getString("masterUrl"))) { if (StringUtils.isNotBlank(object.getString("masterUrl"))) {
result.put(object.getString("masterUrl"), log.getContent()); detailDTO.setResourceName(object.getString("masterUrl"));
return result; return detailDTO;
} }
if (StringUtils.isNotBlank(object.getString("ip"))) { if (StringUtils.isNotBlank(object.getString("ip"))) {
result.put(object.getString("ip"), log.getContent()); detailDTO.setResourceName(object.getString("ip"));
return result; return detailDTO;
} }
result.put(log.getResourceId(), log.getContent()); return detailDTO;
return result; }).collect(Collectors.toList());
}).forEach(log -> logMap.putAll(log.entrySet().stream() }
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)))
);
return logMap; public byte[] downloadLog(String logId) {
LoadTestReportLog loadTestReportLog = loadTestReportLogMapper.selectByPrimaryKey(logId);
if (loadTestReportLog != null) {
return loadTestReportLog.getContent().getBytes();
}
return new byte[0];
} }
} }

View File

@ -53,7 +53,7 @@ public class UserService {
User user1 = userMapper.selectByPrimaryKey(id); User user1 = userMapper.selectByPrimaryKey(id);
if (user1 != null) { if (user1 != null) {
MSException.throwException(Translator.get("user_id_already_exists")); MSException.throwException(Translator.get("user_id_already_exists"));
}else{ } else {
createUser(user); createUser(user);
} }
return getUserDTO(user.getId()); return getUserDTO(user.getId());

View File

@ -22,6 +22,7 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@ -200,12 +201,8 @@ public class WorkspaceService {
} }
public void updateWorkspaceMember(WorkspaceMemberDTO memberDTO) { public void updateWorkspaceMember(WorkspaceMemberDTO memberDTO) {
User user = new User();
BeanUtils.copyProperties(memberDTO, user);
userMapper.updateByPrimaryKeySelective(user);
//
String workspaceId = memberDTO.getWorkspaceId(); String workspaceId = memberDTO.getWorkspaceId();
String userId = user.getId(); String userId = memberDTO.getId();
// 已有角色 // 已有角色
List<Role> memberRoles = extUserRoleMapper.getWorkspaceMemberRoles(workspaceId, userId); List<Role> memberRoles = extUserRoleMapper.getWorkspaceMemberRoles(workspaceId, userId);
// 修改后的角色 // 修改后的角色
@ -234,11 +231,11 @@ public class WorkspaceService {
} }
} }
public Integer checkSourceRole(String orgId, String userId, String roleId) { public Integer checkSourceRole(String workspaceId, String userId, String roleId) {
return extOrganizationMapper.checkSourceRole(orgId, userId, roleId); return extOrganizationMapper.checkSourceRole(workspaceId, userId, roleId);
} }
public void updateWorkspacebyAdmin(Workspace workspace) { public void updateWorkspaceByAdmin(Workspace workspace) {
workspace.setCreateTime(null); workspace.setCreateTime(null);
workspace.setUpdateTime(System.currentTimeMillis()); workspace.setUpdateTime(System.currentTimeMillis());
workspaceMapper.updateByPrimaryKeySelective(workspace); workspaceMapper.updateByPrimaryKeySelective(workspace);

View File

@ -30,6 +30,6 @@ user_email_is_null=User email cannot be null
password_is_null=Password cannot be null password_is_null=Password cannot be null
workspace_not_exists=Workspace is not exists workspace_not_exists=Workspace is not exists
#api #api
api_load_script_error="Load script error" api_load_script_error=Load script error
user_id_already_exists="User ID already exists" user_id_already_exists=User ID already exists
password_modification_failed="Password modification failed" password_modification_failed=Password modification failed

View File

@ -30,6 +30,6 @@ user_email_is_null=用户邮箱不能为空
password_is_null=密码不能为空 password_is_null=密码不能为空
workspace_not_exists=工作空间不存在 workspace_not_exists=工作空间不存在
#api #api
api_load_script_error="读取脚本失败" api_load_script_error=读取脚本失败
user_id_already_exists="用户id已存在" user_id_already_exists=用户id已存在
password_modification_failed="密码修改失败" password_modification_failed=密码修改失败

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="container" v-loading="result.loading"> <div class="container" v-loading="result.loading">
<div class="main-content"> <div class="main-content">
<el-card> <el-card class="table-card">
<template v-slot:header> <template v-slot:header>
<ms-table-header :condition.sync="condition" @search="search" :title="$t('commons.test')" <ms-table-header :condition.sync="condition" @search="search" :title="$t('commons.test')"
:show-create="false"/> :show-create="false"/>

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="container" v-loading="result.loading"> <div class="container" v-loading="result.loading">
<div class="main-content"> <div class="main-content">
<el-card> <el-card class="table-card">
<template v-slot:header> <template v-slot:header>
<ms-table-header :condition.sync="condition" @search="search" :title="$t('commons.test')" <ms-table-header :condition.sync="condition" @search="search" :title="$t('commons.test')"
@create="create" :createTip="$t('load_test.create')"/> @create="create" :createTip="$t('load_test.create')"/>

View File

@ -33,8 +33,8 @@
</span> </span>
<template v-slot:dropdown> <template v-slot:dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<el-dropdown-item command="personal">个人信息</el-dropdown-item> <el-dropdown-item command="personal">{{$t('commons.personal_information')}}</el-dropdown-item>
<el-dropdown-item command="logout">退出系统</el-dropdown-item> <el-dropdown-item command="logout">{{$t('commons.exit_system')}}</el-dropdown-item>
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
</el-dropdown> </el-dropdown>
@ -62,16 +62,16 @@
data() { data() {
return { return {
organizationList: [ organizationList: [
{index: '7-1', name: '无组织'}, {index: '7-1', name: this.$t('organization.none')},
], ],
workspaceList: [ workspaceList: [
{index: '2-1', name: '无工作空间'}, {index: '2-1', name: this.$t('workspace.none')},
], ],
currentUserInfo: {}, currentUserInfo: {},
currentUserId: getCurrentUser().id, currentUserId: getCurrentUser().id,
workspaceIds: [], workspaceIds: [],
currentOrganizationName: '选择组织', currentOrganizationName: this.$t('organization.select'),
currentWorkspaceName: '选择工作空间' currentWorkspaceName: this.$t('workspace.select')
} }
}, },
computed: { computed: {
@ -113,7 +113,7 @@
this.$get("/workspace/list/orgworkspace/", response => { this.$get("/workspace/list/orgworkspace/", response => {
let data = response.data; let data = response.data;
if (data.length === 0) { if (data.length === 0) {
this.workspaceList = [{index: '1-1', name: '无工作区间'}] this.workspaceList = [{index: '1-1', name: this.$t('workspace.none')}]
} else { } else {
this.workspaceList = data; this.workspaceList = data;
let workspace = data.filter(r => r.id === this.currentUser.lastWorkspaceId); let workspace = data.filter(r => r.id === this.currentUser.lastWorkspaceId);
@ -122,7 +122,6 @@
localStorage.setItem(WORKSPACE_ID, workspace[0].id); localStorage.setItem(WORKSPACE_ID, workspace[0].id);
} }
} }
// this.workspaceIds = response.data.map(r = r.id);
}) })
} }
}, },

View File

@ -2,7 +2,7 @@
<div class="container" v-loading="result.loading"> <div class="container" v-loading="result.loading">
<div class="main-content"> <div class="main-content">
<el-card> <el-card class="table-card">
<template v-slot:header> <template v-slot:header>
<div> <div>
<el-row type="flex" justify="space-between" align="middle"> <el-row type="flex" justify="space-between" align="middle">

View File

@ -1,8 +1,8 @@
<template> <template>
<div v-loading="result.loading"> <div v-loading="result.loading">
<el-tabs type="border-card" :stretch="true"> <el-tabs type="border-card" :stretch="true">
<el-tab-pane v-for="(item, key) in logContent" :key="key" :label="key" class="logging-content"> <el-tab-pane v-for="item in logContent" :key="item.id" :label="item.resourceName" class="logging-content">
{{item.substring(0, 2048) }}... {{item.content}}...
<el-link type="primary" @click="downloadLogFile(item)">{{$t('load_test.download_log_file')}}</el-link> <el-link type="primary" @click="downloadLogFile(item)">{{$t('load_test.download_log_file')}}</el-link>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
@ -25,21 +25,28 @@
this.logContent = res.data; this.logContent = res.data;
}) })
}, },
downloadLogFile(content) { downloadLogFile(item) {
const filename = 'jmeter.log' let config = {
const blob = new Blob([content]); url: '/performance/report/log/download/' + item.id,
if ("download" in document.createElement("a")) { method: 'get',
// IE responseType: 'blob'
// chrome/firefox };
let aTag = document.createElement('a'); this.result = this.$request(config).then(response => {
aTag.download = filename; const filename = 'jmeter.log'
aTag.href = URL.createObjectURL(blob); const blob = new Blob([response.data]);
aTag.click(); if ("download" in document.createElement("a")) {
URL.revokeObjectURL(aTag.href) // IE
} else { // chrome/firefox
// IE10+ let aTag = document.createElement('a');
navigator.msSaveBlob(blob, filename); aTag.download = filename;
} aTag.href = URL.createObjectURL(blob);
aTag.click();
URL.revokeObjectURL(aTag.href)
} else {
// IE10+
navigator.msSaveBlob(blob, filename);
}
});
} }
}, },
watch: { watch: {

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="container" v-loading="result.loading"> <div class="container" v-loading="result.loading">
<div class="main-content"> <div class="main-content">
<el-card> <el-card class="table-card">
<template v-slot:header> <template v-slot:header>
<div> <div>
<el-row type="flex" justify="space-between" align="middle"> <el-row type="flex" justify="space-between" align="middle">

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="container"> <div class="container">
<div class="main-content"> <div class="main-content">
<el-card v-loading="result.loading"> <el-card class="table-card" v-loading="result.loading">
<template v-slot:header> <template v-slot:header>
<ms-table-header :condition.sync="condition" @search="search" @create="create" <ms-table-header :condition.sync="condition" @search="search" @create="create"
:create-tip="btnTips" :title="title"/> :create-tip="btnTips" :title="title"/>

View File

@ -1,6 +1,6 @@
<template> <template>
<div v-loading="result.loading"> <div v-loading="result.loading">
<el-card> <el-card class="table-card">
<template v-slot:header> <template v-slot:header>
<ms-table-header :condition.sync="condition" @search="initTableData" @create="create" <ms-table-header :condition.sync="condition" @search="initTableData" @create="create"
:create-tip="btnTips" :title="$t('commons.member')"/> :create-tip="btnTips" :title="$t('commons.member')"/>

View File

@ -1,6 +1,6 @@
<template> <template>
<div> <div>
<el-card v-loading="result.loading"> <el-card class="table-card" v-loading="result.loading">
<template v-slot:header> <template v-slot:header>
<ms-table-header :condition.sync="condition" @search="list" @create="create" <ms-table-header :condition.sync="condition" @search="list" @create="create"
:create-tip="btnTips" :title="$t('commons.workspace')"/> :create-tip="btnTips" :title="$t('commons.workspace')"/>

View File

@ -1,6 +1,6 @@
<template> <template>
<div v-loading="result.loading"> <div v-loading="result.loading">
<el-card> <el-card class="table-card">
<template v-slot:header> <template v-slot:header>
<div> <div>
<el-row type="flex" just ify="space-between" align="middle"> <el-row type="flex" just ify="space-between" align="middle">

View File

@ -1,7 +1,7 @@
<template> <template>
<div v-loading="result.loading"> <div v-loading="result.loading">
<el-card> <el-card class="table-card">
<template v-slot:header> <template v-slot:header>
<ms-table-header :condition.sync="condition" @search="initTableData" @create="create" <ms-table-header :condition.sync="condition" @search="initTableData" @create="create"
:create-tip="btnTips" :title="$t('commons.organization')"/> :create-tip="btnTips" :title="$t('commons.organization')"/>

View File

@ -1,6 +1,6 @@
<template> <template>
<div> <div>
<el-card v-loading="result.loading"> <el-card class="table-card" v-loading="result.loading">
<template v-slot:header> <template v-slot:header>
<ms-table-header :condition.sync="condition" @search="list" @create="create" <ms-table-header :condition.sync="condition" @search="list" @create="create"
:create-tip="btnTips" :title="$t('commons.workspace')"/> :create-tip="btnTips" :title="$t('commons.workspace')"/>

View File

@ -1,7 +1,7 @@
<template> <template>
<div v-loading="result.loading"> <div v-loading="result.loading">
<el-card> <el-card class="table-card">
<template v-slot:header> <template v-slot:header>
<ms-table-header :condition.sync="condition" @search="search" @create="create" <ms-table-header :condition.sync="condition" @search="search" @create="create"
:create-tip="btnTips" :title="$t('commons.test_resource_pool')"/> :create-tip="btnTips" :title="$t('commons.test_resource_pool')"/>

View File

@ -1,7 +1,7 @@
<template> <template>
<div v-loading="result.loading"> <div v-loading="result.loading">
<el-card> <el-card class="table-card">
<template v-slot:header> <template v-slot:header>
<ms-table-header :condition.sync="condition" @search="search" @create="create" <ms-table-header :condition.sync="condition" @search="search" @create="create"
:create-tip="btnTips" :title="$t('commons.member')"/> :create-tip="btnTips" :title="$t('commons.member')"/>

View File

@ -1,6 +1,6 @@
<template> <template>
<div v-loading="result.loading"> <div v-loading="result.loading">
<el-card> <el-card class="table-card">
<template v-slot:header> <template v-slot:header>
<ms-table-header :condition.sync="condition" @search="initTableData" @create="create" <ms-table-header :condition.sync="condition" @search="initTableData" @create="create"
:create-tip="btnTips" :title="$t('commons.member')"/> :create-tip="btnTips" :title="$t('commons.member')"/>

View File

@ -115,6 +115,7 @@
.el-dialog__body { .el-dialog__body {
padding-top: 10px; padding-top: 10px;
padding-bottom: 10px;
} }
.download-template { .download-template {

View File

@ -1,7 +1,7 @@
<template> <template>
<div> <div>
<el-card v-loading="result.loading"> <el-card class="table-card" v-loading="result.loading">
<template v-slot:header> <template v-slot:header>
<ms-table-header :condition.sync="condition" @search="initTableData" <ms-table-header :condition.sync="condition" @search="initTableData"

View File

@ -1,8 +1,6 @@
<template> <template>
<div v-loading="result.loading"> <div v-loading="result.loading">
<el-input :placeholder="$t('test_track.module.search')" v-model="filterText" <el-input :placeholder="$t('test_track.module.search')" v-model="filterText" size="small">
size="small">
<template v-if="type == 'edit'" v-slot:append> <template v-if="type == 'edit'" v-slot:append>
<el-button icon="el-icon-folder-add" @click="openEditNodeDialog('add')"></el-button> <el-button icon="el-icon-folder-add" @click="openEditNodeDialog('add')"></el-button>
</template> </template>
@ -17,100 +15,130 @@
:expand-on-click-node="false" :expand-on-click-node="false"
highlight-current highlight-current
draggable draggable
ref="tree"> ref="tree"
>
<template v-slot:default="{node,data}"> <template v-slot:default="{node,data}">
<span class="custom-tree-node father" @click="handleNodeSelect(node)"> <span class="custom-tree-node father" @click="handleNodeSelect(node)">
<span class="node-icon">
<i class="el-icon-folder"></i>
</span>
<span>{{node.label}}</span> <span class="node-title">{{node.label}}</span>
<el-dropdown v-if="type == 'edit'" class="node-dropdown child"> <span v-if="type == 'edit'" class="node-operate child">
<span class="el-dropdown-link"> <el-tooltip
<i class="el-icon-folder-add"></i> class="item"
</span> effect="dark"
<el-dropdown-menu v-slot:default> open-delay="200"
<el-dropdown-item> :content="$t('test_track.module.rename')"
<div @click="openEditNodeDialog('edit', data)">{{$t('test_track.module.rename')}}</div> placement="top"
</el-dropdown-item> >
<el-dropdown-item> <i @click="openEditNodeDialog('edit', data)" class="el-icon-edit"></i>
<div @click="openEditNodeDialog('add', data)">{{$t('test_track.module.add_submodule')}}</div> </el-tooltip>
</el-dropdown-item> <el-tooltip
<el-dropdown-item> class="item"
<div @click="remove(node, data)">{{$t('commons.delete')}}</div> effect="dark"
</el-dropdown-item> open-delay="200"
</el-dropdown-menu> :content="$t('test_track.module.add_submodule')"
</el-dropdown> placement="top"
>
<i @click="openEditNodeDialog('add', data)" class="el-icon-circle-plus-outline"></i>
</el-tooltip>
<el-tooltip class="item" effect="dark"
open-delay="200" :content="$t('commons.delete')" placement="top">
<i @click="remove(node, data)" class="el-icon-delete"></i>
</el-tooltip>
</span>
<!-- <el-dropdown v-if="type == 'edit'" class="node-dropdown child">
<span class="el-dropdown-link">
<i class="el-icon-folder-add"></i>
</span>
<el-dropdown-menu v-slot:default>
<el-dropdown-item>
<div @click="openEditNodeDialog('edit', data)">{{$t('test_track.module.rename')}}</div>
</el-dropdown-item>
<el-dropdown-item>
<div
@click="openEditNodeDialog('add', data)"
>{{$t('test_track.module.add_submodule')}}</div>
</el-dropdown-item>
<el-dropdown-item>
<div @click="remove(node, data)">{{$t('commons.delete')}}</div>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>-->
</span> </span>
</template> </template>
</el-tree> </el-tree>
<node-edit ref="nodeEdit" :tree-nodes="treeNodes" @refresh="refreshNode"/> <node-edit ref="nodeEdit" :tree-nodes="treeNodes" @refresh="refreshNode" />
</div> </div>
</template> </template>
<script> <script>
import NodeEdit from "./NodeEdit";
import NodeEdit from './NodeEdit'; export default {
name: "NodeTree",
export default { components: { NodeEdit },
name: "NodeTree", data() {
components: {NodeEdit}, return {
data() { result: {},
return { filterText: "",
result: {}, defaultProps: {
filterText: '', children: "children",
defaultProps: { label: "label"
children: 'children',
label: 'label'
}
};
},
props: {
type: {
type: String,
default: 'view'
},
treeNodes: {
type: Array
},
selectNode: {
type: Object
} }
};
},
props: {
type: {
type: String,
default: "view"
}, },
watch: { treeNodes: {
filterText(val) { type: Array
this.$refs.tree.filter(val);
}
}, },
methods: { selectNode: {
handleDragEnd(draggingNode, dropNode, dropType, ev) { type: Object
let param = {}; }
param.id = draggingNode.data.id; },
if (dropType === 'inner') { watch: {
param.parentId = dropNode.data.id; filterText(val) {
param.level = dropNode.data.level + 1; this.$refs.tree.filter(val);
}
},
methods: {
handleDragEnd(draggingNode, dropNode, dropType, ev) {
let param = {};
param.id = draggingNode.data.id;
if (dropType === "inner") {
param.parentId = dropNode.data.id;
param.level = dropNode.data.level + 1;
} else {
if (dropNode.parent.id === 0) {
param.parentId = 0;
param.level = 1;
} else { } else {
if (dropNode.parent.id === 0) { param.parentId = dropNode.parent.data.id;
param.parentId = 0; param.level = dropNode.parent.data.level + 1;
param.level = 1;
} else {
param.parentId = dropNode.parent.data.id;
param.level = dropNode.parent.data.level + 1;
}
} }
this.$post('/case/node/edit', param); }
}, this.$post("/case/node/edit", param);
remove(node, data) { },
this.$alert(this.$t('test_track.module.delete_confirm') + data.label + "" + remove(node, data) {
this.$t('test_track.module.delete_all_resource') + "", '', { this.$alert(
confirmButtonText: this.$t('commons.confirm'), this.$t("test_track.module.delete_confirm") +
callback: (action) => { data.label +
if (action === 'confirm') { "" +
this.$t("test_track.module.delete_all_resource") +
"",
"",
{
confirmButtonText: this.$t("commons.confirm"),
callback: action => {
if (action === "confirm") {
let nodeIds = []; let nodeIds = [];
this.getChildNodeId(node, nodeIds); this.getChildNodeId(node, nodeIds);
this.$post("/case/node/delete", nodeIds, () => { this.$post("/case/node/delete", nodeIds, () => {
@ -118,81 +146,94 @@
const children = parent.data.children || parent.data; const children = parent.data.children || parent.data;
const index = children.findIndex(d => d.id === data.id); const index = children.findIndex(d => d.id === data.id);
children.splice(index, 1); children.splice(index, 1);
this.$success(this.$t('commons.delete_success')); this.$success(this.$t("commons.delete_success"));
this.$emit("refresh"); this.$emit("refresh");
}); });
} }
} }
});
},
handleNodeSelect(node) {
let nodeIds = [];
let nodeNames = [];
this.getChildNodeId(node, nodeIds);
this.getParentNodeName(node, nodeNames);
this.$emit("nodeSelectEvent", nodeIds, nodeNames);
this.$emit("update:selectNode", node);
},
getChildNodeId(rootNode, nodeIds) {
//ID
nodeIds.push(rootNode.data.id);
for (let i = 0; i < rootNode.childNodes.length; i++) {
this.getChildNodeId(rootNode.childNodes[i], nodeIds);
} }
}, );
getParentNodeName(rootNode, nodeNames) { },
if (rootNode.parent && rootNode.parent.id != 0) { handleNodeSelect(node) {
this.getParentNodeName(rootNode.parent, nodeNames) let nodeIds = [];
} let nodeNames = [];
if (rootNode.data.name && rootNode.data.name != '') { this.getChildNodeId(node, nodeIds);
nodeNames.push(rootNode.data.name); this.getParentNodeName(node, nodeNames);
} this.$emit("nodeSelectEvent", nodeIds, nodeNames);
}, this.$emit("update:selectNode", node);
filterNode(value, data) { },
if (!value) return true; getChildNodeId(rootNode, nodeIds) {
return data.label.indexOf(value) !== -1; //ID
}, nodeIds.push(rootNode.data.id);
openEditNodeDialog(type, data) { for (let i = 0; i < rootNode.childNodes.length; i++) {
this.$refs.nodeEdit.open(type, data); this.getChildNodeId(rootNode.childNodes[i], nodeIds);
},
refreshNode() {
this.$emit('refresh');
} }
},
getParentNodeName(rootNode, nodeNames) {
if (rootNode.parent && rootNode.parent.id != 0) {
this.getParentNodeName(rootNode.parent, nodeNames);
}
if (rootNode.data.name && rootNode.data.name != "") {
nodeNames.push(rootNode.data.name);
}
},
filterNode(value, data) {
if (!value) return true;
return data.label.indexOf(value) !== -1;
},
openEditNodeDialog(type, data) {
this.$refs.nodeEdit.open(type, data);
},
refreshNode() {
this.$emit("refresh");
} }
} }
};
</script> </script>
<style scoped> <style scoped>
.el-dropdown-link {
cursor: pointer;
color: #409eff;
}
.el-dropdown-link { .el-icon-arrow-down {
cursor: pointer; font-size: 12px;
color: #409EFF; }
}
.el-icon-arrow-down { .custom-tree-node {
font-size: 12px; flex: 1 1 auto;
} display: flex;
align-items: center;
justify-content: space-between;
font-size: 14px;
padding-right: 8px;
width: 100%;
}
.custom-tree-node { .node-tree {
flex: 1; margin-top: 15px;
display: flex; }
align-items: center;
justify-content: space-between;
font-size: 14px;
padding-right: 8px;
width: 100%;
}
.node-tree { .father .child {
margin-top: 15px; display: none;
} }
.father .child { .father:hover .child {
display: none; display: block;
} }
.father:hover .child { .node-title {
display: block; width: 0px;
} text-overflow: ellipsis;
white-space: nowrap;
flex: 1 1 auto;
padding: 0px 5px;
overflow: hidden;
}
.node-operate > i {
color: #409eff;
margin: 0px 5px;
}
</style> </style>

View File

@ -2,7 +2,7 @@
<div class="container"> <div class="container">
<el-main class="main-content"> <el-main class="main-content">
<el-card v-loading="result.loading"> <el-card class="table-card" v-loading="result.loading">
<template v-slot:header> <template v-slot:header>
<ms-table-header :condition.sync="condition" @search="initTableData" @create="testPlanCreate" <ms-table-header :condition.sync="condition" @search="initTableData" @create="testPlanCreate"
:create-tip="$t('test_track.plan.create_plan')" :title="$t('test_track.plan.test_plan')"/> :create-tip="$t('test_track.plan.create_plan')" :title="$t('test_track.plan.test_plan')"/>

View File

@ -5,7 +5,8 @@
<el-dialog :title="$t('test_track.plan_view.relevance_test_case')" <el-dialog :title="$t('test_track.plan_view.relevance_test_case')"
:visible.sync="dialogFormVisible" :visible.sync="dialogFormVisible"
@close="close" @close="close"
width="50%"> width="60%"
top="50px">
<el-container class="main-content"> <el-container class="main-content">
<el-aside class="tree-aside" width="250px"> <el-aside class="tree-aside" width="250px">
@ -18,12 +19,13 @@
<el-container> <el-container>
<el-main class="case-content" v-loading="result.loading"> <el-main class="case-content" v-loading="result.loading">
<el-scrollbar> <!-- <el-scrollbar> -->
<el-table <el-table
:data="testCases" :data="testCases"
row-key="id" row-key="id"
@select-all="handleSelectAll" @select-all="handleSelectAll"
@select="handleSelectionChange" @select="handleSelectionChange"
height="70vh"
ref="table"> ref="table">
<el-table-column <el-table-column
@ -38,7 +40,7 @@
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
</el-scrollbar> <!-- </el-scrollbar> -->
</el-main> </el-main>
</el-container> </el-container>
</el-container> </el-container>
@ -185,17 +187,18 @@
} }
.case-content { .case-content {
height: 500px; padding: 0px 20px;
height: 100%;
/*border: 1px solid #EBEEF5;*/ /*border: 1px solid #EBEEF5;*/
} }
.tree-aside {
min-height: 300px;
max-height: 100%;
}
.main-content { .main-content {
min-height: 300px; min-height: 300px;
/*border: 1px solid #EBEEF5;*/
}
.el-scrollbar {
height: 100%; height: 100%;
/*border: 1px solid #EBEEF5;*/
} }
</style> </style>

View File

@ -1,5 +1,5 @@
<template> <template>
<el-card v-loading="result.loading"> <el-card class="table-card" v-loading="result.loading">
<template v-slot:header> <template v-slot:header>
<ms-table-header :condition.sync="condition" @search="initTableData" :show-create="false"> <ms-table-header :condition.sync="condition" @search="initTableData" :show-create="false">
<template v-slot:title> <template v-slot:title>

View File

@ -32,3 +32,7 @@ body {
display: table; display: table;
clear: both; clear: both;
} }
.table-card > .el-card__body {
padding-top: 0;
}

View File

@ -51,6 +51,8 @@ export default {
'delete': 'Delete', 'delete': 'Delete',
'not_filled': 'Not filled', 'not_filled': 'Not filled',
'search_by_name': 'Search by name', 'search_by_name': 'Search by name',
'personal_information': 'Personal Information',
'exit_system': 'Exit System',
}, },
workspace: { workspace: {
'create': 'Create Workspace', 'create': 'Create Workspace',
@ -62,6 +64,8 @@ export default {
'organization_name': 'Organization Name', 'organization_name': 'Organization Name',
'please_choose_organization': 'Please Choose Organization', 'please_choose_organization': 'Please Choose Organization',
'please_select_a_workspace_first': 'Please select a workspace first!', 'please_select_a_workspace_first': 'Please select a workspace first!',
'none': 'None Workspace',
'select': 'Select Workspace',
}, },
organization: { organization: {
'create': 'Create', 'create': 'Create',
@ -71,6 +75,8 @@ export default {
'select_organization': 'Please select organization', 'select_organization': 'Please select organization',
'search_by_name': 'Search by name', 'search_by_name': 'Search by name',
'special_characters_are_not_supported': 'Special characters are not supported', 'special_characters_are_not_supported': 'Special characters are not supported',
'none': 'None Organization',
'select': 'Select Organization',
}, },
project: { project: {
'recent': 'Recent Projects', 'recent': 'Recent Projects',

View File

@ -53,6 +53,8 @@ export default {
'not_filled': '未填写', 'not_filled': '未填写',
'please_select': '请选择', 'please_select': '请选择',
'search_by_name': '根据名称搜索', 'search_by_name': '根据名称搜索',
'personal_information': '个人信息',
'exit_system': '退出系统',
}, },
workspace: { workspace: {
'create': '创建工作空间', 'create': '创建工作空间',
@ -64,6 +66,8 @@ export default {
'organization_name': '所属组织', 'organization_name': '所属组织',
'please_choose_organization': '请选择组织', 'please_choose_organization': '请选择组织',
'please_select_a_workspace_first': '请先选择工作空间!', 'please_select_a_workspace_first': '请先选择工作空间!',
'none': '无工作空间',
'select': '选择工作空间',
}, },
organization: { organization: {
'create': '创建组织', 'create': '创建组织',
@ -73,6 +77,8 @@ export default {
'select_organization': '请选择组织', 'select_organization': '请选择组织',
'search_by_name': '根据名称搜索', 'search_by_name': '根据名称搜索',
'special_characters_are_not_supported': '不支持特殊字符', 'special_characters_are_not_supported': '不支持特殊字符',
'none': '无组织',
'select': '选择组织',
}, },
project: { project: {
'recent': '最近的项目', 'recent': '最近的项目',