解决冲突

This commit is contained in:
chenjianxing 2020-03-12 15:57:02 +08:00
commit d16839ec68
55 changed files with 1280 additions and 269 deletions

View File

@ -9,4 +9,6 @@ import java.util.List;
public interface ExtLoadTestReportMapper {
List<ReportDTO> getReportList(@Param("reportRequest")ReportRequest request);
ReportDTO getReportTestAndProInfo(@Param("id") String id);
}

View File

@ -13,4 +13,12 @@
</where>
</select>
<select id="getReportTestAndProInfo" resultType="io.metersphere.dto.ReportDTO">
select ltr.id, ltr.name, ltr.test_id as testId, ltr.description,
ltr.create_time as createTime, ltr.update_time as updateTime, ltr.status as status, lt.name as testName,
p.id as projectId, p.name as projectName
from load_test_report ltr join load_test lt on ltr.test_id = lt.id join project p on lt.project_id = p.id
where ltr.id = #{id}
</select>
</mapper>

View File

@ -1,19 +1,27 @@
package io.metersphere.controller;
import io.metersphere.base.domain.UserRole;
import io.metersphere.controller.request.LoginRequest;
import io.metersphere.dto.UserDTO;
import io.metersphere.i18n.Translator;
import io.metersphere.user.SessionUtils;
import io.metersphere.service.UserService;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.subject.Subject;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
import java.util.stream.Collectors;
@RestController
@RequestMapping
public class LoginController {
@Resource
private UserService userService;
@GetMapping(value = "/isLogin")
public ResultHolder isLogin() {
if (SecurityUtils.getSubject().isAuthenticated()) {
@ -37,6 +45,20 @@ public class LoginController {
try {
subject.login(token);
if (subject.isAuthenticated()) {
UserDTO user = (UserDTO) subject.getSession().getAttribute("user");
// 自动选中组织工作空间
if (StringUtils.isBlank(user.getLastOrganizationId())) {
List<UserRole> userRoles = user.getUserRoles();
List<UserRole> test = userRoles.stream().filter(ur -> ur.getRoleId().indexOf("test") > -1).collect(Collectors.toList());
List<UserRole> org = userRoles.stream().filter(ur -> ur.getRoleId().indexOf("org") > -1).collect(Collectors.toList());
if (test.size() > 0) {
String wsId = test.get(0).getSourceId();
userService.switchUserRole(user, "workspace", wsId);
} else if (org.size() > 0) {
String orgId = org.get(0).getSourceId();
userService.switchUserRole(user, "organization", orgId);
}
}
// 返回 userDTO
return ResultHolder.success(subject.getSession().getAttribute("user"));
} else {

View File

@ -37,6 +37,7 @@ public class OrganizationController {
}
@GetMapping("/delete/{organizationId}")
@RequiresRoles(RoleConstants.ADMIN)
public void deleteOrganization(@PathVariable(value = "organizationId") String organizationId) { organizationService.deleteOrganization(organizationId); }
@PostMapping("/update")

View File

@ -45,4 +45,12 @@ public class ReportController {
public void deleteReport(@PathVariable String reportId) {
reportService.deleteReport(reportId);
}
@GetMapping("/test/pro/info/{reportId}")
public ReportDTO getReportTestAndProInfo(@PathVariable String reportId) {
return reportService.getReportTestAndProInfo(reportId);
}
}

View File

@ -2,7 +2,6 @@ package io.metersphere.controller;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import io.metersphere.base.domain.Role;
import io.metersphere.base.domain.User;
import io.metersphere.commons.constants.RoleConstants;
import io.metersphere.commons.utils.PageUtils;
@ -12,10 +11,10 @@ import io.metersphere.controller.request.member.AddMemberRequest;
import io.metersphere.controller.request.member.QueryMemberRequest;
import io.metersphere.controller.request.organization.AddOrgMemberRequest;
import io.metersphere.controller.request.organization.QueryOrgMemberRequest;
import io.metersphere.dto.OrganizationMemberDTO;
import io.metersphere.dto.UserDTO;
import io.metersphere.dto.UserRoleDTO;
import io.metersphere.service.OrganizationService;
import io.metersphere.service.UserService;
import io.metersphere.service.WorkspaceService;
import io.metersphere.user.SessionUser;
import io.metersphere.user.SessionUtils;
import org.apache.shiro.authz.annotation.Logical;
@ -31,40 +30,94 @@ public class UserController {
@Resource
private UserService userService;
@Resource
private OrganizationService organizationService;
@Resource
private WorkspaceService workspaceService;
@PostMapping("/add")
// admin api
@PostMapping("/special/add")
@RequiresRoles(RoleConstants.ADMIN)
public UserDTO insertUser(@RequestBody User user) {
return userService.insert(user);
}
@GetMapping("/list")
public List<User> getUserList() {
return userService.getUserList();
}
@PostMapping("/list/{goPage}/{pageSize}")
@PostMapping("/special/list/{goPage}/{pageSize}")
@RequiresRoles(RoleConstants.ADMIN)
public Pager<List<User>> getUserList(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody UserRequest request) {
Page<Object> page = PageHelper.startPage(goPage, pageSize, true);
return PageUtils.setPageInfo(page, userService.getUserListWithRequest(request));
}
@GetMapping("/delete/{userId}")
@GetMapping("/special/delete/{userId}")
@RequiresRoles(RoleConstants.ADMIN)
public void deleteUser(@PathVariable(value = "userId") String userId) {
userService.deleteUser(userId);
}
@PostMapping("/update")
@PostMapping("/special/update")
@RequiresRoles(RoleConstants.ADMIN)
public void updateUser(@RequestBody User user) {
userService.updateUser(user);
}
/**
* 修改登录用户信息
*/
@PostMapping("/special/ws/member/list/{goPage}/{pageSize}")
@RequiresRoles(RoleConstants.ADMIN)
public Pager<List<User>> getMemberListByAdmin(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody QueryMemberRequest request) {
Page<Object> page = PageHelper.startPage(goPage, pageSize, true);
return PageUtils.setPageInfo(page, userService.getMemberList(request));
}
@PostMapping("/special/ws/member/list/all")
@RequiresRoles(RoleConstants.ADMIN)
public List<User> getMemberListByAdmin(@RequestBody QueryMemberRequest request) {
return userService.getMemberList(request);
}
@PostMapping("/special/ws/member/add")
@RequiresRoles(RoleConstants.ADMIN)
public void addMemberByAdmin(@RequestBody AddMemberRequest request) {
userService.addMember(request);
}
@GetMapping("/special/ws/member/delete/{workspaceId}/{userId}")
@RequiresRoles(RoleConstants.ADMIN)
public void deleteMemberByAdmin(@PathVariable String workspaceId, @PathVariable String userId) {
userService.deleteMember(workspaceId, userId);
}
@PostMapping("/special/org/member/add")
@RequiresRoles(RoleConstants.ADMIN)
public void addOrganizationMemberByAdmin(@RequestBody AddOrgMemberRequest request) {
userService.addOrganizationMember(request);
}
@GetMapping("/special/org/member/delete/{organizationId}/{userId}")
@RequiresRoles(RoleConstants.ADMIN)
public void delOrganizationMemberByAdmin(@PathVariable String organizationId, @PathVariable String userId) {
userService.delOrganizationMember(organizationId, userId);
}
@PostMapping("/special/org/member/list/{goPage}/{pageSize}")
@RequiresRoles(RoleConstants.ADMIN)
public Pager<List<User>> getOrgMemberListByAdmin(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody QueryOrgMemberRequest request) {
Page<Object> page = PageHelper.startPage(goPage, pageSize, true);
return PageUtils.setPageInfo(page, userService.getOrgMemberList(request));
}
@PostMapping("/special/org/member/list/all")
@RequiresRoles(RoleConstants.ADMIN)
public List<User> getOrgMemberListByAdmin(@RequestBody QueryOrgMemberRequest request) {
return userService.getOrgMemberList(request);
}
// admin api
@GetMapping("/list")
@RequiresRoles(value = {RoleConstants.ADMIN,RoleConstants.ORG_ADMIN}, logical = Logical.OR)
public List<User> getUserList() {
return userService.getUserList();
}
@PostMapping("/update/currentuser")
public UserDTO updateCurrentUser(@RequestBody User user) {
SessionUser sessionUser = SessionUtils.getUser();
@ -73,20 +126,19 @@ public class UserController {
return SessionUtils.getUser();
}
@GetMapping("/role/list/{userId}")
public List<Role> getUserRolesList(@PathVariable(value = "userId") String userId) {
return userService.getUserRolesList(userId);
}
@GetMapping("/rolelist/{userId}")
public List<UserRoleDTO> convertUserRoleDTO(@PathVariable(value = "userId") String userId) {
return userService.getUserRoleList(userId);
}
@PostMapping("/switch/source/{sign}/{sourceId}")
public UserDTO switchUserRole(@PathVariable String sign, @PathVariable(value = "sourceId") String sourceId) {
@PostMapping("/switch/source/org/{sourceId}")
@RequiresRoles(RoleConstants.ORG_ADMIN)
public UserDTO switchOrganization(@PathVariable(value = "sourceId") String sourceId) {
UserDTO user = SessionUtils.getUser();
userService.switchUserRole(user, sign, sourceId);
userService.switchUserRole(user,"organization",sourceId);
return SessionUtils.getUser();
}
@PostMapping("/switch/source/ws/{sourceId}")
@RequiresRoles(value = {RoleConstants.TEST_MANAGER,RoleConstants.TEST_VIEWER,RoleConstants.TEST_USER}, logical = Logical.OR)
public UserDTO switchWorkspace(@PathVariable(value = "sourceId") String sourceId) {
UserDTO user = SessionUtils.getUser();
userService.switchUserRole(user, "workspace", sourceId);
return SessionUtils.getUser();
}
@ -98,8 +150,9 @@ public class UserController {
/**
* 获取工作空间成员用户
*/
@PostMapping("/member/list/{goPage}/{pageSize}")
//@RequiresRoles(RoleConstants.TEST_MANAGER)
@PostMapping("/ws/member/list/{goPage}/{pageSize}")
@RequiresRoles(value = {RoleConstants.ORG_ADMIN,RoleConstants.TEST_MANAGER,
RoleConstants.TEST_USER,RoleConstants.TEST_VIEWER}, logical = Logical.OR)
public Pager<List<User>> getMemberList(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody QueryMemberRequest request) {
Page<Object> page = PageHelper.startPage(goPage, pageSize, true);
return PageUtils.setPageInfo(page, userService.getMemberList(request));
@ -108,51 +161,59 @@ public class UserController {
/**
* 获取工作空间成员用户 不分页
*/
@PostMapping("/member/list/all")
@PostMapping("/ws/member/list/all")
@RequiresRoles(value = {RoleConstants.ORG_ADMIN,RoleConstants.TEST_MANAGER,
RoleConstants.TEST_USER,RoleConstants.TEST_VIEWER}, logical = Logical.OR)
public List<User> getMemberList(@RequestBody QueryMemberRequest request) {
return userService.getMemberList(request);
}
/**
* 添加成员
* 添加工作空间成员
*/
@PostMapping("/member/add")
@RequiresRoles(value = {RoleConstants.TEST_MANAGER,RoleConstants.ORG_ADMIN,RoleConstants.ADMIN}, logical = Logical.OR)
@PostMapping("/ws/member/add")
@RequiresRoles(value = {RoleConstants.TEST_MANAGER,RoleConstants.ORG_ADMIN}, logical = Logical.OR)
public void addMember(@RequestBody AddMemberRequest request) {
String wsId = request.getWorkspaceId();
workspaceService.checkWorkspaceOwner(wsId);
userService.addMember(request);
}
/**
* 删除成员
* 删除工作空间成员
*/
@GetMapping("/member/delete/{workspaceId}/{userId}")
@RequiresRoles(value = {RoleConstants.TEST_MANAGER, RoleConstants.ADMIN, RoleConstants.ORG_ADMIN}, logical = Logical.OR)
@GetMapping("/ws/member/delete/{workspaceId}/{userId}")
@RequiresRoles(value = {RoleConstants.TEST_MANAGER,RoleConstants.ORG_ADMIN}, logical = Logical.OR)
public void deleteMember(@PathVariable String workspaceId, @PathVariable String userId) {
workspaceService.checkWorkspaceOwner(workspaceId);
userService.deleteMember(workspaceId, userId);
}
/**
* 添加组织成员
*/
@PostMapping("/orgmember/add")
@RequiresRoles(value = {RoleConstants.ADMIN, RoleConstants.ORG_ADMIN}, logical = Logical.OR)
@PostMapping("/org/member/add")
@RequiresRoles(RoleConstants.ORG_ADMIN)
public void addOrganizationMember(@RequestBody AddOrgMemberRequest request) {
organizationService.checkOrgOwner(request.getOrganizationId());
userService.addOrganizationMember(request);
}
/**
* 删除组织成员
*/
@GetMapping("/orgmember/delete/{organizationId}/{userId}")
@RequiresRoles(value = {RoleConstants.ADMIN,RoleConstants.ORG_ADMIN}, logical = Logical.OR)
@GetMapping("/org/member/delete/{organizationId}/{userId}")
@RequiresRoles(RoleConstants.ORG_ADMIN)
public void delOrganizationMember(@PathVariable String organizationId, @PathVariable String userId) {
organizationService.checkOrgOwner(organizationId);
userService.delOrganizationMember(organizationId, userId);
}
/**
* 查询组织成员列表
*/
@PostMapping("/orgmember/list/{goPage}/{pageSize}")
@PostMapping("/org/member/list/{goPage}/{pageSize}")
@RequiresRoles(value = {RoleConstants.ORG_ADMIN,RoleConstants.TEST_MANAGER}, logical = Logical.OR)
public Pager<List<User>> getOrgMemberList(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody QueryOrgMemberRequest request) {
Page<Object> page = PageHelper.startPage(goPage, pageSize, true);
return PageUtils.setPageInfo(page, userService.getOrgMemberList(request));
@ -161,23 +222,12 @@ public class UserController {
/**
* 组织成员列表不分页
*/
@PostMapping("/orgmember/list/all")
@PostMapping("/org/member/list/all")
@RequiresRoles(value = {RoleConstants.ORG_ADMIN,RoleConstants.TEST_MANAGER}, logical = Logical.OR)
public List<User> getOrgMemberList(@RequestBody QueryOrgMemberRequest request) {
return userService.getOrgMemberList(request);
}
/**
* 查询组织成员列表 带角色信息
*/
@PostMapping("/orgmemberdto/list/{goPage}/{pageSize}")
public Pager<List<OrganizationMemberDTO>> getOrganizationMemberDTO(@PathVariable int goPage, @PathVariable int pageSize, @RequestBody QueryOrgMemberRequest request) {
Page<Object> page = PageHelper.startPage(goPage, pageSize, true);
return PageUtils.setPageInfo(page, userService.getOrganizationMemberDTO(request));
}
/**
*
*/
@GetMapping("/besideorg/list/{orgId}")
public List<User> getBesideOrgMemberList(@PathVariable String orgId) {
return userService.getBesideOrgMemberList(orgId);

View File

@ -26,6 +26,7 @@ public class UserRoleController {
}
@GetMapping("/list/ws/{workspaceId}/{userId}")
@RequiresRoles(value = {RoleConstants.ADMIN,RoleConstants.ORG_ADMIN}, logical = Logical.OR)
public List<Role> getWorkspaceMemberRole(@PathVariable String workspaceId, @PathVariable String userId) {
return userRoleService.getWorkspaceMemberRoles(workspaceId, userId);
}

View File

@ -40,7 +40,7 @@ public class WorkspaceController {
@PostMapping("update")
@RequiresRoles(RoleConstants.ORG_ADMIN)
public Workspace updateWorkspace(@RequestBody Workspace workspace) {
workspaceService.checkOwner(workspace.getId());
workspaceService.checkWorkspaceOwnerByOrgAdmin(workspace.getId());
return workspaceService.saveWorkspace(workspace);
}
@ -53,7 +53,7 @@ public class WorkspaceController {
@GetMapping("delete/{workspaceId}")
@RequiresRoles(RoleConstants.ORG_ADMIN)
public void deleteWorkspace(@PathVariable String workspaceId) {
workspaceService.checkOwner(workspaceId);
workspaceService.checkWorkspaceOwnerByOrgAdmin(workspaceId);
workspaceService.deleteWorkspace(workspaceId);
}

View File

@ -11,6 +11,24 @@ public class ReportDTO {
private String status;
private String content;
private String testName;
private String projectId;
private String projectName;
public String getProjectId() {
return projectId;
}
public void setProjectId(String projectId) {
this.projectId = projectId;
}
public String getProjectName() {
return projectName;
}
public void setProjectName(String projectName) {
this.projectName = projectName;
}
public String getId() {
return id;

View File

@ -4,18 +4,11 @@ import io.metersphere.base.domain.*;
import io.metersphere.base.mapper.FileContentMapper;
import io.metersphere.base.mapper.FileMetadataMapper;
import io.metersphere.base.mapper.LoadTestFileMapper;
import org.springframework.core.io.InputStreamResource;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
@Service
@ -27,17 +20,6 @@ public class FileService {
@Resource
private FileContentMapper fileContentMapper;
// 将上传的文件保存在内存方便测试
private Map<String, MultipartFile> fileMap = new ConcurrentHashMap<>();
public void upload(String name, MultipartFile file) throws IOException {
String result = new BufferedReader(new InputStreamReader(file.getInputStream()))
.lines().collect(Collectors.joining("\n"));
System.out.println(String.format("upload file: %s, content: \n%s", name, result));
fileMap.put(name, file);
}
public byte[] loadFileAsBytes(String id) {
FileContent fileContent = fileContentMapper.selectByPrimaryKey(id);

View File

@ -6,8 +6,13 @@ import io.metersphere.base.mapper.UserMapper;
import io.metersphere.base.mapper.UserRoleMapper;
import io.metersphere.base.mapper.ext.ExtOrganizationMapper;
import io.metersphere.base.mapper.ext.ExtUserRoleMapper;
import io.metersphere.commons.constants.RoleConstants;
import io.metersphere.commons.exception.MSException;
import io.metersphere.dto.OrganizationMemberDTO;
import io.metersphere.dto.UserRoleHelpDTO;
import io.metersphere.i18n.Translator;
import io.metersphere.user.SessionUser;
import io.metersphere.user.SessionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
@ -109,4 +114,16 @@ public class OrganizationService {
public Integer checkSourceRole(String orgId, String userId, String roleId) {
return extOrganizationMapper.checkSourceRole(orgId, userId, roleId);
}
public void checkOrgOwner(String organizationId) {
SessionUser user = SessionUtils.getUser();
List<String> collect = user.getUserRoles().stream()
.filter(ur -> RoleConstants.ORG_ADMIN.equals(ur.getRoleId()))
.map(UserRole::getSourceId)
.collect(Collectors.toList());
if (!collect.contains(organizationId)) {
MSException.throwException(Translator.get("organization_does_not_belong_to_user"));
}
}
}

View File

@ -37,4 +37,8 @@ public class ReportService {
public void deleteReport(String reportId) {
loadTestReportMapper.deleteByPrimaryKey(reportId);
}
public ReportDTO getReportTestAndProInfo(String reportId) {
return extLoadTestReportMapper.getReportTestAndProInfo(reportId);
}
}

View File

@ -128,7 +128,7 @@ public class UserService {
userMapper.updateByPrimaryKeySelective(user);
}
public List<Role> getUserRolesList(String userId) {
/*public List<Role> getUserRolesList(String userId) {
UserRoleExample userRoleExample = new UserRoleExample();
userRoleExample.createCriteria().andUserIdEqualTo(userId);
List<UserRole> userRolesList = userRoleMapper.selectByExample(userRoleExample);
@ -143,7 +143,7 @@ public class UserService {
return new ArrayList<>();
}
return convertUserRoleDTO(extUserRoleMapper.getUserRoleHelpList(userId));
}
}*/
private List<UserRoleDTO> convertUserRoleDTO(List<UserRoleHelpDTO> helpDTOList) {
StringBuilder buffer = new StringBuilder();

View File

@ -91,15 +91,16 @@ public class WorkspaceService {
}
/**
* ORG_ADMIN 需要检查是否有操作此工作空间的权限
* ORG_ADMIN需要检查是否有操作此工作空间的权限
*/
public void checkOwner(String workspaceId) {
public void checkWorkspaceOwnerByOrgAdmin(String workspaceId) {
checkWorkspaceIsExist(workspaceId);
WorkspaceExample example = new WorkspaceExample();
SessionUser user = SessionUtils.getUser();
List<String> orgIds = user.getUserRoles().stream()
.filter(ur -> RoleConstants.ORG_ADMIN.equals(ur.getRoleId()))
.map(UserRole::getSourceId)
.collect(Collectors.toList());
WorkspaceExample example = new WorkspaceExample();
example.createCriteria()
.andOrganizationIdIn(orgIds)
.andIdEqualTo(workspaceId);
@ -108,6 +109,48 @@ public class WorkspaceService {
}
}
public void checkWorkspaceOwnerByTestManager(String workspaceId) {
checkWorkspaceIsExist(workspaceId);
SessionUser user = SessionUtils.getUser();
List<String> wsIds = user.getUserRoles().stream()
.filter(ur -> RoleConstants.TEST_MANAGER.equals(ur.getRoleId()))
.map(UserRole::getSourceId)
.collect(Collectors.toList());
boolean contains = wsIds.contains(workspaceId);
if (!contains) {
MSException.throwException(Translator.get("workspace_does_not_belong_to_user"));
}
}
public void checkWorkspaceOwner(String workspaceId) {
checkWorkspaceIsExist(workspaceId);
WorkspaceExample example = new WorkspaceExample();
SessionUser user = SessionUtils.getUser();
List<String> orgIds = user.getUserRoles().stream()
.filter(ur -> RoleConstants.ORG_ADMIN.equals(ur.getRoleId()))
.map(UserRole::getSourceId)
.collect(Collectors.toList());
example.createCriteria()
.andOrganizationIdIn(orgIds)
.andIdEqualTo(workspaceId);
List<String> wsIds = user.getUserRoles().stream()
.filter(ur -> RoleConstants.TEST_MANAGER.equals(ur.getRoleId()))
.map(UserRole::getSourceId)
.collect(Collectors.toList());
boolean contains = wsIds.contains(workspaceId);
if (workspaceMapper.countByExample(example) == 0 && !contains) {
MSException.throwException(Translator.get("workspace_does_not_belong_to_user"));
}
}
public void checkWorkspaceIsExist(String workspaceId) {
WorkspaceExample example = new WorkspaceExample();
example.createCriteria().andIdEqualTo(workspaceId);
if (workspaceMapper.countByExample(example) == 0) {
MSException.throwException("workspace_not_exist");
}
}
public List<Workspace> getWorkspaceListByUserId(String userId) {
List<UserRoleHelpDTO> userRoleHelpList = extUserRoleMapper.getUserRoleHelpList(userId);
List<String> workspaceIds = new ArrayList<>();

View File

@ -5,5 +5,6 @@
"project_name_already_exists": "The project name already exists",
"workspace_name_is_null": "Workspace name cannot be null",
"workspace_name_already_exists": "The workspace name already exists",
"workspace_does_not_belong_to_user": "The current workspace does not belong to the current user"
"workspace_does_not_belong_to_user": "The current workspace does not belong to the current user",
"organization_does_not_belong_to_user": "The current organization does not belong to the current user"
}

View File

@ -5,5 +5,6 @@
"project_name_already_exists": "项目名称已存在",
"workspace_name_is_null": "工作空间名不能为空",
"workspace_name_already_exists": "工作空间名已存在",
"workspace_does_not_belong_to_user": "当前工作空间不属于当前用户"
"workspace_does_not_belong_to_user": "当前工作空间不属于当前用户",
"organization_does_not_belong_to_user": "当前组织不属于当前用户"
}

View File

@ -40,7 +40,9 @@
"plugin:vue/essential",
"eslint:recommended"
],
"rules": {},
"rules": {
"vue/no-unused-components": "off"
},
"parserOptions": {
"parser": "babel-eslint"
}

View File

@ -0,0 +1,30 @@
import {ROLE_ORG_ADMIN, ROLE_TEST_MANAGER, ROLE_TEST_USER, ROLE_TEST_VIEWER, TokenKey} from "./constants";
export function hasRole(role) {
let user = JSON.parse(localStorage.getItem(TokenKey));
let roles = user.roles.map(r => r.id);
return roles.indexOf(role) > -1;
}
export function hasRoles(...roles) {
let user = JSON.parse(localStorage.getItem(TokenKey));
let rs = user.roles.map(r => r.id);
for (let item of roles) {
if (rs.indexOf(item) > -1) {
return true;
}
}
return false;
}
export function checkoutCurrentOrganization() {
let user = JSON.parse(localStorage.getItem(TokenKey));
// 查看当前用户是否是 lastOrganizationId 的组织管理员
return user.userRoles.filter(ur => hasRole(ROLE_ORG_ADMIN) && user.lastOrganizationId === ur.sourceId).length > 0;
}
export function checkoutCurrentWorkspace() {
let user = JSON.parse(localStorage.getItem(TokenKey));
// 查看当前用户是否是 lastWorkspaceId 的工作空间用户
return user.userRoles.filter(ur => hasRoles(ROLE_TEST_MANAGER, ROLE_TEST_USER, ROLE_TEST_VIEWER) && user.lastWorkspaceId === ur.sourceId).length > 0;
}

View File

@ -33,6 +33,13 @@ export default {
'status': 'Enable/Disable',
'show_all': 'Show All',
'report': 'Report',
'user': 'User',
'system': 'System',
'personal_setting': 'Personal Setting',
'test_resource_pool': 'Resource Pool',
'system_setting': 'Settings',
'functional': 'Functional',
'performance': 'Performance',
},
workspace: {
'create': 'Create Workspace',
@ -71,6 +78,7 @@ export default {
'search_by_name': 'Search by name',
'modify_personal_info': 'Modify Personal Information',
'input_name': 'Please enter a user name',
'input_email': 'Please enter a email',
'special_characters_are_not_supported': 'Special characters are not supported',
'mobile_number_format_is_incorrect': 'Mobile number format is incorrect',
'email_format_is_incorrect': 'Email format is incorrect',
@ -93,6 +101,10 @@ export default {
'recent': 'Recent Report',
'search_by_name': 'Search by Name',
'test_name': 'Test',
'test_overview': 'Test Overview',
'test_request_statistics': 'Test Request Statistics',
'test_error_log': 'Test Error Log',
'test_log_details': 'Test Log Details'
},
load_test: {
'recent': 'Recent Tests',

View File

@ -33,6 +33,13 @@ export default {
'status': '启用/禁用',
'show_all': '显示全部',
'report': '报告',
'user': '用户',
'system': '系统',
'personal_setting': '个人设置',
'test_resource_pool': '测试资源池',
'system_setting': '系统设置',
'functional': '功能测试',
'performance': '性能测试',
},
workspace: {
'create': '创建工作空间',
@ -94,6 +101,10 @@ export default {
'recent': '最近的报告',
'search_by_name': '根据名称搜索',
'test_name': '所属测试',
'test_overview': '测试概览',
'test_request_statistics': '请求统计',
'test_error_log': '错误记录',
'test_log_details': '日志详情'
},
load_test: {
'recent': '最近的测试',

View File

@ -11,6 +11,7 @@
<ms-user/>
</el-col>
</el-row>
<ms-view/>
<ms-web-socket/>
</el-col>
@ -26,7 +27,7 @@
name: 'app',
data() {
return {
auth: false,
auth: false
}
},
beforeCreate() {

View File

@ -6,9 +6,11 @@
<el-menu-item :index="'/' + beaseUrl + '/home'">
{{ $t("i18n.home") }}
</el-menu-item>
<el-submenu index="3" popper-class="submenu" v-permission="['test_manager']">
<el-submenu index="3" popper-class="submenu" v-permission="['test_manager']" v-if="isCurrentWorkspaceUser">
<template slot="title">{{$t('commons.project')}}</template>
<ms-recent-project/>
<performance-recent-project v-if="beaseUrl == 'performance'"/>
<functional-recent-project v-if="beaseUrl == 'functional'"/>
<el-divider/>
<el-menu-item :index="'/' + beaseUrl + '/project/all'">
<font-awesome-icon :icon="['fa', 'list-ul']"/>
@ -18,7 +20,9 @@
<el-button type="text">{{$t('project.create')}}</el-button>
</el-menu-item>
</el-submenu>
<el-submenu index="4" popper-class="submenu" v-permission="['test_manager', 'test_user']">
<el-submenu index="4" popper-class="submenu" v-permission="['test_manager', 'test_user']"
v-if="isCurrentWorkspaceUser">
<template slot="title">{{$t('commons.test')}}</template>
<performance-recent-test-plan v-if="beaseUrl == 'performance'"/>
<functional-recent-test-plan v-if="beaseUrl == 'functional'"/>
@ -31,7 +35,9 @@
<el-button type="text">{{$t('load_test.create')}}</el-button>
</el-menu-item>
</el-submenu>
<el-submenu index="5" popper-class="submenu" v-permission="['test_manager', 'test_user', 'test_viewer']">
<el-submenu index="5" popper-class="submenu" v-permission="['test_manager', 'test_user', 'test_viewer']"
v-if="isCurrentWorkspaceUser">
<template slot="title">{{$t('commons.report')}}</template>
<performance-recent-report v-if="beaseUrl == 'performance'"/>
<functional-recent-report v-if="beaseUrl == 'functional'"/>
@ -41,9 +47,12 @@
<span style="padding-left: 5px;">{{$t('commons.show_all')}}</span>
</el-menu-item>
</el-submenu>
<router-link class="header-bottom" :to="'/' + beaseUrl + '/plan/create'" v-permission="['test_user','test_manager']">
<router-link class="header-bottom" :to="'/' + beaseUrl + '/plan/create'" v-permission="['test_user','test_manager']"
v-if="isCurrentWorkspaceUser">
<el-button type="primary" size="small">{{$t('load_test.create')}}</el-button>
</router-link>
</el-menu>
</div>
@ -53,19 +62,31 @@
import PerformanceRecentTestPlan from "./testPlan/PerformanceRecentTestPlan";
import FunctionalRecentTestPlan from "./testPlan/FunctionalRecentTestPlan";
import MsRecentProject from "./project/RecentProject";
import PerformanceRecentProject from "./project/PerformanceRecentProject";
import FunctionalRecentProject from "./project/FunctionalRecentProject";
import PerformanceRecentReport from "./report/PerformanceRecentReport";
import FunctionalRecentReport from "./report/FunctionalRecentReport";
import {checkoutCurrentWorkspace} from "../../common/utils";
export default {
name: "MsMenus",
components: {PerformanceRecentReport, PerformanceRecentTestPlan, MsRecentProject, FunctionalRecentTestPlan, FunctionalRecentReport},
props: {
beaseUrl: {
type: String
components: {PerformanceRecentReport, PerformanceRecentTestPlan, FunctionalRecentTestPlan, FunctionalRecentReport,
PerformanceRecentProject,FunctionalRecentProject},
data() {
return {
isCurrentWorkspaceUser: false,
}
},
props: {
beaseUrl: {
type: String
}
},
mounted() {
this.isCurrentWorkspaceUser = checkoutCurrentWorkspace();
}
}
</script>
<style>

View File

@ -1,33 +0,0 @@
<template>
<el-row class="settings" type="flex" justify="end" align="middle">
<router-link to="/content">
<font-awesome-icon :icon="['fas', 'user-plus']" size="lg"/>
</router-link>
<router-link to="/setting">
<font-awesome-icon :icon="['fas', 'cog']" size="lg"/>
</router-link>
</el-row>
</template>
<script>
export default {
name: "MsSetting"
}
</script>
<style scoped>
.settings > * {
padding-left: 15px;
cursor: pointer;
line-height: 40px;
color: inherit;
}
.settings > * :hover {
opacity: 0.7;
}
.settings > * :active {
opacity: 0.8;
}
</style>

View File

@ -7,14 +7,17 @@
:default-active="activeIndex"
@select="handleSelect"
router>
<el-menu-item index="/functional" v-permission="['test_manager','test_user','test_viewer']">
功能测试
{{$t('commons.functional')}}
</el-menu-item>
<el-menu-item index="/performance" onselectstart="return false"
v-permission="['test_manager','test_user','test_viewer']">
性能测试
{{$t('commons.performance')}}
</el-menu-item>
<el-menu-item index="/setting/personsetting" onselectstart="return false">
{{$t('commons.system_setting')}}
</el-menu-item>
<el-menu-item index="/setting/personsetting" onselectstart="return false">系统设置</el-menu-item>
</el-menu>
</template>

View File

@ -42,7 +42,8 @@
</template>
<script>
import {ROLE_TEST_MANAGER, ROLE_TEST_USER, ROLE_TEST_VIEWER, TokenKey} from '../../common/constants';
import {ROLE_ORG_ADMIN, ROLE_TEST_MANAGER, ROLE_TEST_USER, ROLE_TEST_VIEWER, TokenKey} from '../../common/constants';
import {hasRoles} from "../../common/utils";
export default {
name: "MsUser",
@ -89,18 +90,17 @@
}
},
initMenuData() {
let roles = this.currentUser.roles.map(r => r.id);
// if (roles.indexOf(ROLE_ORG_ADMIN) > -1) {
this.$get("/organization/list/userorg/" + this.currentUserId, response => {
let data = response.data;
this.organizationList = data;
let org = data.filter(r => r.id === this.currentUser.lastOrganizationId);
if (org.length > 0) {
this.currentOrganizationName = org[0].name;
}
});
// }
if (roles.indexOf(ROLE_TEST_MANAGER) > -1 || roles.indexOf(ROLE_TEST_USER) > -1 || roles.indexOf(ROLE_TEST_VIEWER) > -1) {
if (hasRoles(ROLE_ORG_ADMIN, ROLE_TEST_VIEWER, ROLE_TEST_USER, ROLE_TEST_MANAGER)) {
this.$get("/organization/list/userorg/" + this.currentUserId, response => {
let data = response.data;
this.organizationList = data;
let org = data.filter(r => r.id === this.currentUser.lastOrganizationId);
if (org.length > 0) {
this.currentOrganizationName = org[0].name;
}
});
}
if (hasRoles(ROLE_TEST_VIEWER, ROLE_TEST_USER, ROLE_TEST_MANAGER)) {
if (!this.currentUser.lastOrganizationId) {
return false;
}
@ -126,19 +126,17 @@
},
changeOrg(data) {
let orgId = data.id;
let sign = "organization";
this.$post("/user/switch/source/" + sign + "/" + orgId, {}, response => {
this.$post("/user/switch/source/org/" + orgId, {}, response => {
localStorage.setItem(TokenKey, JSON.stringify(response.data));
window.location.reload();
})
},
changeWs(data) {
let sign = "workspace";
let workspaceId = data.id;
if (!workspaceId) {
return false;
}
this.$post("/user/switch/source/" + sign + "/" + workspaceId, {}, response => {
this.$post("/user/switch/source/ws/" + workspaceId, {}, response => {
localStorage.setItem(TokenKey, JSON.stringify(response.data));
window.location.reload();
})

View File

@ -6,7 +6,7 @@
</div>
<el-menu-item :key="p.id" v-for="p in recentProjects"
:index="'/loadtest/' + p.id" :route="{name:'loadtest', params:{projectId:p.id, projectName:p.name}}">
:index="'/functional/' + p.id" :route="{name:'fucPlan', params:{projectId:p.id, projectName:p.name}}">
{{ p.name }}
</el-menu-item>
</el-menu>
@ -15,14 +15,13 @@
<script>
import {ROLE_TEST_MANAGER, ROLE_TEST_USER, ROLE_TEST_VIEWER} from "../../../common/constants";
import {hasRoles} from "../../../common/utils";
export default {
name: "MsRecentProject",
name: "FunctionalRecentProject",
mounted() {
const rolesString = localStorage.getItem("roles");
const roles = rolesString.split(',');
if (roles.indexOf(ROLE_TEST_MANAGER) > -1 || roles.indexOf(ROLE_TEST_USER) > -1 || roles.indexOf(ROLE_TEST_VIEWER) > -1) {
if (hasRoles(ROLE_TEST_VIEWER, ROLE_TEST_USER, ROLE_TEST_MANAGER)) {
this.$get('/project/recent/5', (response) => {
this.recentProjects = response.data;
});

View File

@ -90,18 +90,23 @@
},
}
},
props: {
beaseUrl: {
type: String
}
},
mounted() {
if (this.$route.path.split('/')[2] === 'create') {
if (this.$route.path.split('/')[3] === 'create') {
this.create();
this.$router.push('/project/all');
this.$router.push( '/' + this.beaseUrl + '/project/all');
}
this.list();
},
watch: {
'$route'(to) {
if (to.path.split('/')[2] === 'create') {
if (to.path.split('/')[3] === 'create') {
this.create();
this.$router.push('/project/all');
this.$router.push('/' + this.beaseUrl + '/project/all');
}
}
},

View File

@ -1,14 +1,193 @@
<template>
<div>
功能测试图表
</div>
<chart :options="bar"></chart>
</template>
<script>
import echarts from 'echarts'
export default {
name: "PerformanceChart"
name: "PerformanceChart",
data() {
return {
bar: {
backgroundColor: '#394056',
title: {
top: 20,
text: 'Requests',
textStyle: {
fontWeight: 'normal',
fontSize: 16,
color: '#F1F1F3'
},
left: '1%'
},
tooltip: {
trigger: 'axis',
axisPointer: {
lineStyle: {
color: '#57617B'
}
}
},
legend: {
top: 20,
icon: 'rect',
itemWidth: 14,
itemHeight: 5,
itemGap: 13,
data: ['CMCC', 'CTCC', 'CUCC'],
right: '4%',
textStyle: {
fontSize: 12,
color: '#F1F1F3'
}
},
grid: {
top: 100,
left: '2%',
right: '2%',
bottom: '2%',
containLabel: true
},
xAxis: [{
type: 'category',
boundaryGap: false,
axisLine: {
lineStyle: {
color: '#57617B'
}
},
data: ['13:00', '13:05', '13:10', '13:15', '13:20', '13:25', '13:30', '13:35', '13:40', '13:45', '13:50', '13:55']
}],
yAxis: [{
type: 'value',
name: '(%)',
axisTick: {
show: false
},
axisLine: {
lineStyle: {
color: '#57617B'
}
},
axisLabel: {
margin: 10,
textStyle: {
fontSize: 14
}
},
splitLine: {
lineStyle: {
color: '#57617B'
}
}
}],
series: [{
name: 'CMCC',
type: 'line',
smooth: true,
symbol: 'circle',
symbolSize: 5,
showSymbol: false,
lineStyle: {
normal: {
width: 1
}
},
areaStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: 'rgba(137, 189, 27, 0.3)'
}, {
offset: 0.8,
color: 'rgba(137, 189, 27, 0)'
}], false),
shadowColor: 'rgba(0, 0, 0, 0.1)',
shadowBlur: 10
}
},
itemStyle: {
normal: {
color: 'rgb(137,189,27)',
borderColor: 'rgba(137,189,2,0.27)',
borderWidth: 12
}
},
data: [220, 182, 191, 134, 150, 120, 110, 125, 145, 122, 165, 122]
}, {
name: 'CTCC',
type: 'line',
smooth: true,
symbol: 'circle',
symbolSize: 5,
showSymbol: false,
lineStyle: {
normal: {
width: 1
}
},
areaStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: 'rgba(0, 136, 212, 0.3)'
}, {
offset: 0.8,
color: 'rgba(0, 136, 212, 0)'
}], false),
shadowColor: 'rgba(0, 0, 0, 0.1)',
shadowBlur: 10
}
},
itemStyle: {
normal: {
color: 'rgb(0,136,212)',
borderColor: 'rgba(0,136,212,0.2)',
borderWidth: 12
}
},
data: [120, 110, 125, 145, 122, 165, 122, 220, 182, 191, 134, 150]
}, {
name: 'CUCC',
type: 'line',
smooth: true,
symbol: 'circle',
symbolSize: 5,
showSymbol: false,
lineStyle: {
normal: {
width: 1
}
},
areaStyle: {
normal: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{
offset: 0,
color: 'rgba(219, 50, 51, 0.3)'
}, {
offset: 0.8,
color: 'rgba(219, 50, 51, 0)'
}], false),
shadowColor: 'rgba(0, 0, 0, 0.1)',
shadowBlur: 10
}
},
itemStyle: {
normal: {
color: 'rgb(219,50,51)',
borderColor: 'rgba(219,50,51,0.2)',
borderWidth: 12
}
},
data: [220, 182, 125, 145, 122, 191, 134, 150, 120, 110, 165, 122]
}]
},
}
}
}
</script>

View File

@ -0,0 +1,44 @@
<template>
<el-menu router menu-trigger="click" :default-active="$route.path">
<div class="recent-text">
<i class="el-icon-time"/>
{{$t('project.recent')}}
</div>
<el-menu-item :key="p.id" v-for="p in recentProjects"
:index="'/performance/plan/' + p.id" :route="{name:'perPlan', params:{projectId:p.id, projectName:p.name}}">
{{ p.name }}
</el-menu-item>
</el-menu>
</template>
<script>
import {ROLE_TEST_MANAGER, ROLE_TEST_USER, ROLE_TEST_VIEWER} from "../../../common/constants";
import {hasRoles} from "../../../common/utils";
export default {
name: "PerformanceRecentProject",
mounted() {
if (hasRoles(ROLE_TEST_VIEWER, ROLE_TEST_USER, ROLE_TEST_MANAGER)) {
this.$get('/project/recent/5', (response) => {
this.recentProjects = response.data;
});
}
},
methods: {},
data() {
return {
recentProjects: [],
}
}
}
</script>
<style scoped>
.recent-text {
padding-left: 10%;
color: #777777;
}
</style>

View File

@ -5,7 +5,7 @@
{{$t('load_test.recent')}}
</div>
<el-menu-item :key="p.id" v-for="p in recentReports"
:index="'/report/' + p.id" :route="{name:'report', params:{projectId:p.id, projectName:p.name}}">
:index="'/functional/report/view/' + p.id" :route="{path: '/functional/report/view/' + p.id}">
{{ p.name }}
</el-menu-item>
</el-menu>

View File

@ -8,9 +8,9 @@
<span class="title">{{$t('commons.report')}}</span>
<span class="search">
<el-input type="text" size="small" :placeholder="$t('report.search_by_name')"
prefix-icon="el-icon-search"
maxlength="60"
v-model="condition" @change="search" clearable/>
prefix-icon="el-icon-search"
maxlength="60"
v-model="condition" @change="search" clearable/>
</span>
</el-row>
</div>
@ -129,8 +129,10 @@
handleSelectionChange(val) {
this.multipleSelection = val;
},
handleEdit() {
handleEdit(report) {
this.$router.push({
path: '/functional/reportView/' + report.id
})
},
handleDelete(report) {
this.$alert(this.$t('load_test.delete_confirm') + report.name + "", '', {

View File

@ -5,7 +5,7 @@
{{$t('load_test.recent')}}
</div>
<el-menu-item :key="p.id" v-for="p in recentReports"
:index="'/report/' + p.id" :route="{name:'report', params:{projectId:p.id, projectName:p.name}}">
:index="'/performance/report/view/' + p.id" :route="{path: '/performance/report/view/' + p.id}">
{{ p.name }}
</el-menu-item>
</el-menu>
@ -13,14 +13,13 @@
<script>
import {ROLE_TEST_MANAGER, ROLE_TEST_USER, ROLE_TEST_VIEWER} from "../../../common/constants";
import {hasRoles} from "../../../common/utils";
export default {
name: "PerformanceRecentReport",
mounted() {
const rolesString = localStorage.getItem("roles");
const roles = rolesString.split(',');
if (roles.indexOf(ROLE_TEST_MANAGER) > -1 || roles.indexOf(ROLE_TEST_USER) > -1 || roles.indexOf(ROLE_TEST_VIEWER) > -1) {
if (hasRoles(ROLE_TEST_VIEWER, ROLE_TEST_USER, ROLE_TEST_MANAGER)) {
this.$get('/report/recent/5', (response) => {
this.recentReports = response.data;
});

View File

@ -0,0 +1,141 @@
<template>
<div v-loading="result.loading" class="report-view-container">
<div class="main-content">
<el-card>
<el-row>
<el-col :span="16">
<el-row>
<el-breadcrumb separator-class="el-icon-arrow-right">
<el-breadcrumb-item :to="{ path: '/' }">{{projectName}}</el-breadcrumb-item>
<el-breadcrumb-item>{{testName}}</el-breadcrumb-item>
<el-breadcrumb-item>{{reportName}}</el-breadcrumb-item>
</el-breadcrumb>
</el-row>
<el-row class="ms-report-view-btns">
<el-button type="primary" plain size="mini">立即停止</el-button>
<el-button type="success" plain size="mini">再次执行</el-button>
<el-button type="info" plain size="mini">导出</el-button>
<el-button type="warning" plain size="mini">比较</el-button>
</el-row>
</el-col>
<el-col :span="8">
<span class="ms-report-time-desc">
持续时间 30 分钟
</span>
<span class="ms-report-time-desc">
开始时间 2020-3-10 12:00:00
</span>
<span class="ms-report-time-desc">
结束时间 2020-3-10 12:30:00
</span>
</el-col>
</el-row>
<el-divider></el-divider>
<el-tabs v-model="active" type="border-card" :stretch="true">
<el-tab-pane :label="$t('report.test_overview')">
<ms-report-test-overview />
</el-tab-pane>
<el-tab-pane :label="$t('report.test_request_statistics')">
<ms-report-request-statistics />
</el-tab-pane>
<el-tab-pane :label="$t('report.test_error_log')">
<ms-report-error-log />
</el-tab-pane>
<el-tab-pane :label="$t('report.test_log_details')">
<ms-report-log-details />
</el-tab-pane>
</el-tabs>
</el-card>
</div>
</div>
</template>
<script>
import MsReportErrorLog from './components/ErrorLog';
import MsReportLogDetails from './components/LogDetails';
import MsReportRequestStatistics from './components/RequestStatistics';
import MsReportTestOverview from './components/TestOverview';
export default {
name: "PerformanceReportView",
components: {
MsReportErrorLog,
MsReportLogDetails,
MsReportRequestStatistics,
MsReportTestOverview
},
data() {
return {
result: {},
active: '0',
reportId: '',
reportName: '',
testName: '',
projectName: ''
}
},
methods: {
initBreadcrumb() {
if(this.reportId){
this.result = this.$get("report/test/pro/info/" + this.reportId, res => {
let data = res.data;
if(data){
this.reportName = data.name;
this.testName = data.testName;
this.projectName = data.projectName;
}
})
}
}
},
created() {
this.reportId = this.$route.path.split('/')[4];
this.initBreadcrumb();
},
watch: {
'$route'(to) {
let reportId = to.path.split('/')[4];
if(reportId){
this.$get("report/test/pro/info/" + reportId, response => {
let data = response.data;
if(data){
this.reportName = data.name;
this.testName = data.testName;
this.projectName = data.projectName;
}
});
}
}
}
}
</script>
<style scoped>
.report-view-container {
float: none;
text-align: center;
padding: 15px;
width: 100%;
height: 100%;
box-sizing: border-box;
}
.report-view-container .main-content {
margin: 0 auto;
width: 100%;
max-width: 1200px;
}
.ms-report-view-btns {
margin-top: 15px;
}
.ms-report-time-desc {
text-align: left;
display: block;
color: #5C7878;
}
</style>

View File

@ -129,8 +129,10 @@
handleSelectionChange(val) {
this.multipleSelection = val;
},
handleEdit() {
handleEdit(report) {
this.$router.push({
path: '/performance/report/view/' + report.id
})
},
handleDelete(report) {
this.$alert(this.$t('load_test.delete_confirm') + report.name + "", '', {

View File

@ -0,0 +1,49 @@
<template>
<div>
<el-table
:data="tableData"
border
style="width: 100%"
:default-sort = "{prop: 'elementLabel'}"
>
<el-table-column
prop="typeOfError"
label="Type of Error"
sortable>
</el-table-column>
<el-table-column
prop="numberOfErrors"
label="Number of errors"
sortable>
</el-table-column>
<el-table-column
prop="error"
label="% in errors"
sortable>
</el-table-column>
<el-table-column
prop="allSamples"
label="% in all samples"
sortable>
</el-table-column>
</el-table>
</div>
</template>
<script>
export default {
name: "ErrorLog",
data() {
return {
tableData: [{},{},{},{},{}]
}
},
methods: {
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,15 @@
<template>
<div>
LogDetails
</div>
</template>
<script>
export default {
name: "LogDetails"
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,97 @@
<template>
<div>
<el-table
:data="tableData"
border
style="width: 100%"
:default-sort = "{prop: 'elementLabel'}"
>
<el-table-column
prop="elementLabel"
label="Element Label"
fixed
width="150"
sortable>
</el-table-column>
<el-table-column
prop="samples"
label="Samples"
width="150"
sortable>
</el-table-column>
<el-table-column
prop="avgResponseTime"
label="Avg Response Time(ms)"
width="220"
sortable>
</el-table-column>
<el-table-column
prop="avgHits"
label="Avg Hits/s"
width="150"
sortable>
</el-table-column>
<el-table-column
prop="90line"
label="90% line(ms)"
width="150"
sortable>
</el-table-column>
<el-table-column
prop="95line"
label="95% line(ms)"
width="150"
sortable>
</el-table-column>
<el-table-column
prop="99line"
label="99% line(ms)"
width="150"
sortable>
</el-table-column>
<el-table-column
prop="minResponseTime"
label="Min Response Time(ms)"
width="220"
sortable>
</el-table-column>
<el-table-column
prop="maxResponseTime"
label="Max Response Time(ms)"
width="220"
sortable>
</el-table-column>
<el-table-column
prop="avgBandwidth"
label="Avg Bandwidth(KBytes/s)"
width="220"
sortable>
</el-table-column>
<el-table-column
prop="errorPercentage"
label="Error Percentage"
width="180"
fixed="right"
sortable>
</el-table-column>
</el-table>
</div>
</template>
<script>
export default {
name: "RequestStatistics",
data() {
return {
tableData: [{},{},{},{},{}]
}
},
methods: {
}
}
</script>
<style scoped>
</style>

View File

@ -0,0 +1,229 @@
<template>
<div>
<el-row :gutter="12">
<el-col :span="4">
<el-card shadow="always" class="ms-card-index-1">
<span class="ms-card-data">
<span class="ms-card-data-digital">40</span>
<span class="ms-card-data-unit"> VU</span>
</span>
<span class="ms-card-desc">Max Users</span>
</el-card>
</el-col>
<el-col :span="4">
<el-card shadow="always" class="ms-card-index-2">
<span class="ms-card-data">
<span class="ms-card-data-digital">5.4</span>
<span class="ms-card-data-unit"> Hits/s</span>
</span>
<span class="ms-card-desc">Avg.Throughput</span>
</el-card>
</el-col>
<el-col :span="4">
<el-card shadow="always" class="ms-card-index-3">
<span class="ms-card-data">
<span class="ms-card-data-digital">0.41</span>
<span class="ms-card-data-unit"> %</span>
</span>
<span class="ms-card-desc">Errors</span>
</el-card>
</el-col>
<el-col :span="4">
<el-card shadow="always" class="ms-card-index-4">
<span class="ms-card-data">
<span class="ms-card-data-digital">1.28</span>
<span class="ms-card-data-unit"> s</span>
</span>
<span class="ms-card-desc">Avg.Response Time</span>
</el-card>
</el-col>
<el-col :span="4">
<el-card shadow="always" class="ms-card-index-5">
<span class="ms-card-data">
<span class="ms-card-data-digital">1.41</span>
<span class="ms-card-data-unit"> s</span>
</span>
<span class="ms-card-desc">90% Response Time</span>
</el-card>
</el-col>
<el-col :span="4">
<el-card shadow="always" class="ms-card-index-6">
<span class="ms-card-data">
<span class="ms-card-data-digital">817.29</span>
<span class="ms-card-data-unit"> KiB/s</span>
</span>
<span class="ms-card-desc">Avg.Bandwidth</span>
</el-card>
</el-col>
</el-row>
<el-row>
<el-col :span="12">
<chart ref="chart1" :options="option1" :autoresize="true"></chart>
</el-col>
<el-col :span="12">
<chart ref="chart2" :options="option2" :autoresize="true"></chart>
</el-col>
</el-row>
</div>
</template>
<script>
export default {
name: "TestOverview",
data() {
return {
option1: {
legend: {
top: 20,
data: ['Users', 'Hits/s', 'Error(s)']
},
xAxis: {
type: 'category',
data: ['12:40', '12:50', '13:00', '13:10', '13:20', '13:30', '13:40']
},
yAxis: [{
name: 'User',
type: 'value',
},
{
name: 'Hits/s',
type: 'value'
}
],
series: [
{
name: 'Users',
color: '#0CA74A',
data: [20, 40, 40, 40, 40, 40, 40],
type: 'line',
},
{
name: 'Hits/s',
color: '#65A2FF',
data: [15, 38, 35, 39, 36, 37, 5],
type: 'line',
},
{
name: 'Error(s)',
color: '#E6113C',
data: [0, 0, 0, 0, 0, 0, 0],
type: 'line',
}
]
},
option2: {
legend: {
top: 20,
data: ['Users', 'Response Time']
},
xAxis: {
type: 'category',
data: ['12:40', '12:50', '13:00', '13:10', '13:20', '13:30', '13:40']
},
yAxis: [{
name: 'User',
type: 'value',
},
{
name: 'Response Time',
type: 'value'
}
],
series: [
{
name: 'Users',
color: '#0CA74A',
data: [20, 40, 40, 40, 40, 40, 40],
type: 'line',
},
{
name: 'Response Time',
color: '#99743C',
data: [15, 38, 35, 39, 36, 37, 5],
type: 'line',
}
]
}
}
}
}
</script>
<style scoped>
.ms-card-data {
text-align: left;
display: block;
margin-bottom: 5px;
}
.ms-card-desc {
display: block;
text-align: left;
}
.ms-card-data-digital {
font-size: 21px;
}
.ms-card-data-unit {
color: #8492a6;
font-size: 15px;
}
.ms-card-index-1 .ms-card-data-digital {
color: #44b349;
}
.ms-card-index-1 {
border-left-color: #44b349;
border-left-width: 3px;
}
.ms-card-index-2 .ms-card-data-digital {
color: #65A2FF;
}
.ms-card-index-2 {
border-left-color: #65A2FF;
border-left-width: 3px;
}
.ms-card-index-3 .ms-card-data-digital {
color: #E6113C;
}
.ms-card-index-3 {
border-left-color: #E6113C;
border-left-width: 3px;
}
.ms-card-index-4 .ms-card-data-digital {
color: #99743C;
}
.ms-card-index-4 {
border-left-color: #99743C;
border-left-width: 3px;
}
.ms-card-index-5 .ms-card-data-digital {
color: #99743C;
}
.ms-card-index-5 {
border-left-color: #99743C;
border-left-width: 3px;
}
.ms-card-index-6 .ms-card-data-digital {
color: #3C9899;
}
.ms-card-index-6 {
border-left-color: #3C9899;
border-left-width: 3px;
}
</style>

View File

@ -22,6 +22,7 @@ import EditFunctionalTestPlan from "../testPlan/EditFunctionalTestPlan";
import PerformanceTestHome from "../testPlan/PerformanceTestHome";
import FunctionalTestPlan from "../testPlan/FunctionalTestPlan";
import FunctionalTestHome from "../testPlan/FunctionalTestHome";
import PerformanceReportView from "../report/PerformanceReportView";
Vue.use(VueRouter);
@ -84,6 +85,7 @@ const router = new VueRouter({
children: [
{
path: 'home',
name: 'fucHome',
component: FunctionalTestHome,
},
{
@ -93,6 +95,7 @@ const router = new VueRouter({
},
{
path: "plan/edit/:testId",
name: "editFucTest",
component: EditFunctionalTestPlan,
props: {
content: (route) => {
@ -104,14 +107,17 @@ const router = new VueRouter({
},
{
path: "plan/:projectId",
name: "fucPlan",
component: FunctionalTestPlan
},
{
path: "project/:type",
name: "fucProject",
component: MsProject
},
{
path: "report/:type",
name: "fucReport",
component: FunctionalTestReport
}
]
@ -126,6 +132,7 @@ const router = new VueRouter({
children: [
{
path: 'home',
name: 'perHome',
component: PerformanceTestHome,
},
{
@ -135,6 +142,7 @@ const router = new VueRouter({
},
{
path: "plan/edit/:testId",
name: "editPerTest",
component: EditPerformanceTestPlan,
props: {
content: (route) => {
@ -146,19 +154,28 @@ const router = new VueRouter({
},
{
path: "plan/:projectId",
name: "perPlan",
component: PerformanceTestPlan
},
{
path: "project/:type",
name: "perProject",
component: MsProject
},
{
path: "report/:type",
name: "perReport",
component: PerformanceTestReport
},
{
path: "chart",
name: "perChart",
component: PerformanceChart
},
{
path: "report/view/:reportId",
name: "perReportView",
component: PerformanceReportView
}
]
}

View File

@ -3,45 +3,59 @@
<el-submenu index="1" v-permission="['admin']">
<template slot="title">
<font-awesome-icon class="icon account" :icon="['far', 'address-card']" size="lg"/>
<span>系统</span>
<span>{{$t('commons.system')}}</span>
</template>
<el-menu-item index="/setting/user">用户</el-menu-item>
<el-menu-item index="/setting/organization">组织</el-menu-item>
<el-menu-item index="/setting/systemworkspace">工作空间</el-menu-item>
<el-menu-item index="/setting/testresourcepool">测试资源池</el-menu-item>
<el-menu-item index="/setting/user">{{$t('commons.user')}}</el-menu-item>
<el-menu-item index="/setting/organization">{{$t('commons.organization')}}</el-menu-item>
<el-menu-item index="/setting/systemworkspace">{{$t('commons.workspace')}}</el-menu-item>
<el-menu-item index="/setting/testresourcepool">{{$t('commons.test_resource_pool')}}</el-menu-item>
</el-submenu>
<el-submenu index="2" v-permission="['org_admin']">
<el-submenu index="2" v-permission="['org_admin']" v-if="isCurrentOrganizationAdmin">
<template slot="title">
<font-awesome-icon class="icon organization" :icon="['far', 'building']" size="lg"/>
<span>组织</span>
<span>{{$t('commons.organization')}}</span>
</template>
<el-menu-item index="/setting/organizationmember" v-permission="['org_admin']">成员</el-menu-item>
<el-menu-item index="/setting/organizationworkspace" v-permission="['org_admin']">工作空间</el-menu-item>
<el-menu-item index="/setting/organizationmember" v-permission="['org_admin']">{{$t('commons.member')}}
</el-menu-item>
<el-menu-item index="/setting/organizationworkspace" v-permission="['org_admin']">{{$t('commons.workspace')}}
</el-menu-item>
</el-submenu>
<el-submenu index="3" v-permission="['test_manager','test_user','test_viewer']">
<el-submenu index="3" v-permission="['test_manager','test_user','test_viewer']" v-if="isCurrentWorkspaceUser">
<template slot="title">
<font-awesome-icon class="icon workspace" :icon="['far', 'list-alt']" size="lg"/>
<span>工作空间</span>
<span>{{$t('commons.workspace')}}</span>
</template>
<el-menu-item index="/setting/member">成员</el-menu-item>
<el-menu-item index="/setting/member">{{$t('commons.member')}}</el-menu-item>
</el-submenu>
<el-submenu index="4">
<template slot="title">
<font-awesome-icon class="icon" :icon="['far', 'user']" size="lg"/>
<span>个人</span>
<span>{{$t('commons.personal_info')}}</span>
</template>
<el-menu-item index="/setting/personsetting">个人设置</el-menu-item>
<el-menu-item index="/setting/personsetting">{{$t('commons.personal_setting')}}</el-menu-item>
</el-submenu>
</el-menu>
</template>
<script>
import {checkoutCurrentOrganization, checkoutCurrentWorkspace} from "../../../common/utils";
export default {
name: "MsSettingMenu"
name: "MsSettingMenu",
data() {
return {
isCurrentOrganizationAdmin: false,
isCurrentWorkspaceUser: false,
}
},
mounted() {
this.isCurrentOrganizationAdmin = checkoutCurrentOrganization();
this.isCurrentWorkspaceUser = checkoutCurrentWorkspace();
},
}
</script>

View File

@ -129,7 +129,7 @@
createVisible: false,
updateVisible: false,
form: {},
queryPath: "/user/orgmember/list",
queryPath: "/user/org/member/list",
condition: "",
tableData: [],
rules: {
@ -221,7 +221,7 @@
cancelButtonText: this.$t('commons.cancel'),
type: 'warning'
}).then(() => {
this.result = this.$get('/user/orgmember/delete/' + this.currentUser().lastOrganizationId + '/' + row.id, () => {
this.result = this.$get('/user/org/member/delete/' + this.currentUser().lastOrganizationId + '/' + row.id, () => {
this.$message({
type: 'success',
message: this.$t('commons.delete_success')
@ -262,7 +262,7 @@
roleIds: this.form.roleIds,
organizationId: orgId
};
this.result = this.$post("user/orgmember/add", param,() => {
this.result = this.$post("user/org/member/add", param,() => {
this.initTableData();
this.createVisible = false;
})

View File

@ -256,7 +256,7 @@
name: '',
workspaceId: this.items[i].id
}
let path = "user/member/list/all";
let path = "user/ws/member/list/all";
this.$post(path, param, res => {
let member = res.data;
this.$set(this.items[i], "memberSize", member.length);
@ -297,7 +297,7 @@
name: '',
workspaceId: row.id
};
let path = "/user/member/list";
let path = "/user/ws/member/list";
this.result = this.$post(this.buildPagePath(path), param, res => {
let data = res.data;
this.memberLineData = data.listObject;
@ -335,7 +335,7 @@
roleIds: this.memberForm.roleIds,
workspaceId: this.currentWorkspaceRow.id
};
this.result = this.$post("user/member/add", param,() => {
this.result = this.$post("user/ws/member/add", param,() => {
this.cellClick(this.currentWorkspaceRow);
this.addMemberVisible = false;
})
@ -360,7 +360,7 @@
cancelButtonText: this.$t('commons.cancel'),
type: 'warning'
}).then(() => {
this.result = this.$get('/user/member/delete/' + this.currentWorkspaceRow.id + '/' + row.id, () => {
this.result = this.$get('/user/ws/member/delete/' + this.currentWorkspaceRow.id + '/' + row.id, () => {
this.$message({
type: 'success',
message: this.$t('commons.delete_success')

View File

@ -290,7 +290,7 @@
name: '',
organizationId: row.id
};
let path = "/user/orgmember/list";
let path = "/user/special/org/member/list";
this.result = this.$post(this.buildPagePath(path), param, res => {
let data = res.data;
this.memberLineData = data.listObject;
@ -330,7 +330,7 @@
cancelButtonText: this.$t('commons.cancel'),
type: 'warning'
}).then(() => {
this.result = this.$get('/user/orgmember/delete/' + this.currentRow.id + '/' + row.id, () => {
this.result = this.$get('/user/special/org/member/delete/' + this.currentRow.id + '/' + row.id, () => {
this.$message({
type: 'success',
message: this.$t('commons.delete_success')
@ -385,7 +385,7 @@
name: '',
organizationId: this.tableData[i].id
}
let path = "user/orgmember/list/all";
let path = "user/special/org/member/list/all";
this.$post(path, param, res => {
let member = res.data;
this.$set(this.tableData[i], "memberSize", member.length);
@ -431,7 +431,7 @@
roleIds: this.memberForm.roleIds,
organizationId: this.currentRow.id
};
this.result = this.$post("user/orgmember/add", param,() => {
this.result = this.$post("user/special/org/member/add", param,() => {
this.cellClick(this.currentRow);
this.addMemberVisible = false;
})

View File

@ -270,7 +270,7 @@
name: '',
workspaceId: row.id
};
let path = "/user/member/list";
let path = "/user/special/ws/member/list";
this.result = this.$post(this.buildPagePath(path), param, res => {
let data = res.data;
this.memberLineData = data.listObject;
@ -340,7 +340,7 @@
name: '',
workspaceId: this.items[i].id
}
let path = "user/member/list/all";
let path = "user/special/ws/member/list/all";
this.$post(path, param, res => {
let member = res.data;
this.$set(this.items[i], "memberSize", member.length);
@ -376,7 +376,7 @@
roleIds: this.memberForm.roleIds,
workspaceId: this.currentWorkspaceRow.id
};
this.result = this.$post("user/member/add", param,() => {
this.result = this.$post("user/special/ws/member/add", param,() => {
this.cellClick(this.currentWorkspaceRow);
this.addMemberVisible = false;
})
@ -401,7 +401,7 @@
cancelButtonText: this.$t('commons.cancel'),
type: 'warning'
}).then(() => {
this.result = this.$get('/user/member/delete/' + this.currentWorkspaceRow.id + '/' + row.id, () => {
this.result = this.$get('/user/special/ws/member/delete/' + this.currentWorkspaceRow.id + '/' + row.id, () => {
this.$message({
type: 'success',
message: this.$t('commons.delete_success')

View File

@ -113,10 +113,10 @@
export default {
data() {
return {
queryPath: '/user/list',
deletePath: '/user/delete/',
createPath: '/user/add',
updatePath: '/user/update',
queryPath: '/user/special/list',
deletePath: '/user/special/delete/',
createPath: '/user/special/add',
updatePath: '/user/special/update',
result: {},
createVisible: false,
updateVisible: false,

View File

@ -3,7 +3,7 @@
<el-card>
<div slot="header">
<el-row type="flex" justify="space-between" align="middle" v-permission="['test_manager']">
<span class="title">成员
<span class="title">{{$t('commons.member')}}
<ms-create-box :tips="btnTips" :exec="create"/>
</span>
<span class="search">
@ -126,7 +126,7 @@
btnTips: "添加工作空间成员",
createVisible: false,
updateVisible: false,
queryPath: "/user/member/list",
queryPath: "/user/ws/member/list",
condition: "",
tableData: [],
rules: {
@ -198,7 +198,7 @@
type: 'warning'
}).then(() => {
this.loading = true;
this.$get('/user/member/delete/' + this.currentUser().lastWorkspaceId + '/' + row.id).then(() => {
this.$get('/user/ws/member/delete/' + this.currentUser().lastWorkspaceId + '/' + row.id).then(() => {
this.initTableData();
this.loading = false;
});
@ -256,7 +256,7 @@
});
return false;
}
this.$post('/user/orgmember/list/all', param,response => {
this.$post('/user/org/member/list/all', param,response => {
this.createVisible = true;
this.$set(this.form, "userList", response.data);
})
@ -272,7 +272,7 @@
roleIds: this.form.roleIds,
workspaceId: this.currentUser().lastWorkspaceId
};
this.result = this.$post("user/member/add", param, () => {
this.result = this.$post("user/ws/member/add", param, () => {
this.initTableData();
this.createVisible = false;
})

View File

@ -81,10 +81,13 @@
window.location.reload();
return;
}
let testId = to.path.split('/')[4]; // find testId
if (testId) {
this.$get('/testplan/get/' + testId, response => {
this.testPlan = response.data;
if(response.data){
this.testPlan = response.data;
}
});
}
}

View File

@ -4,7 +4,7 @@
<i class="el-icon-time"/>
{{$t('load_test.recent')}}
</div>
<el-menu-item :key="t.id" v-for="t in recentTestPlans" :index="'/editTest/' + t.id">
<el-menu-item :key="t.id" v-for="t in recentTestPlans" :index="'/functional/plan/edit/' + t.id">
{{ t.name }}
</el-menu-item>
</el-menu>
@ -12,14 +12,13 @@
<script>
import {ROLE_TEST_MANAGER, ROLE_TEST_USER, ROLE_TEST_VIEWER} from "../../../common/constants";
import {hasRoles} from "../../../common/utils";
export default {
name: "PerformanceRecentTestPlan",
mounted() {
const rolesString = localStorage.getItem("roles");
const roles = rolesString.split(',');
if (roles.indexOf(ROLE_TEST_MANAGER) > -1 || roles.indexOf(ROLE_TEST_USER) > -1 || roles.indexOf(ROLE_TEST_VIEWER) > -1) {
if (hasRoles(ROLE_TEST_VIEWER, ROLE_TEST_USER, ROLE_TEST_MANAGER)) {
this.$get('/testplan/recent/5', (response) => {
this.recentTestPlans = response.data;
});

View File

@ -4,7 +4,7 @@
<div>
<transition>
<keep-alive>
<router-view/>
<router-view :beaseUrl="beaseUrl"/>
</keep-alive>
</transition>
</div>

View File

@ -1,6 +1,6 @@
<template>
<div>
<h1>能测试首页</h1>
<h1>能测试首页</h1>
</div>
</template>

View File

@ -4,7 +4,7 @@
<i class="el-icon-time"/>
{{$t('load_test.recent')}}
</div>
<el-menu-item :key="t.id" v-for="t in recentTestPlans" :index="'/editTest/' + t.id">
<el-menu-item :key="t.id" v-for="t in recentTestPlans" :index="'/performance/plan/edit/' + t.id">
{{ t.name }}
</el-menu-item>
</el-menu>

View File

@ -4,7 +4,7 @@
<div>
<transition>
<keep-alive>
<router-view/>
<router-view :beaseUrl="beaseUrl"/>
</keep-alive>
</transition>
</div>

View File

@ -7,7 +7,7 @@
<script>
export default {
name: "PerformanceTestHome"
name: "PerformanceTestHome"
}
</script>

View File

@ -217,7 +217,10 @@
}
},
watch: {
'$route'(to) {
'$route'(to, from) {
if(from.name != 'createPerTest' || from.name != 'editPerTest'){
return;
}
let testId = to.path.split('/')[4];
if (testId) {
this.getAdvancedConfig(testId);

View File

@ -64,7 +64,7 @@
<el-input-number
placeholder=""
:min="1"
:max="Math.min(threadNumber, duration)"
:max="Math.min(threadNumber, rampUpTime)"
v-model="step"
@change="calculateChart"
size="mini"/>
@ -75,8 +75,7 @@
</el-form>
</el-col>
<el-col :span="12">
压力预估图
<chart ref="chart1" :options="orgOptions" :auto-resize="true"></chart>
<chart class="chart-container" ref="chart1" :options="orgOptions" :autoresize="true"></chart>
</el-col>
</el-row>
</div>
@ -113,7 +112,10 @@
}
},
watch: {
'$route'(to) {
'$route'(to, from) {
if(from.name != 'createPerTest' || from.name != 'editPerTest'){
return;
}
let testId = to.path.split('/')[4];
if (testId) {
this.getLoadConfig(testId);
@ -124,52 +126,56 @@
},
methods: {
getLoadConfig(testId) {
this.$get('/testplan/get-load-config/' + testId, (response) => {
if (response.data) {
let data = JSON.parse(response.data);
if(testId) {
data.forEach(d => {
switch (d.key) {
case TARGET_LEVEL:
this.threadNumber = d.value;
break;
case RAMP_UP:
this.rampUpTime = d.value;
break;
case DURATION:
this.duration = d.value;
break;
case STEPS:
this.step = d.value;
break;
case RPS_LIMIT:
this.rpsLimit = d.value;
break;
default:
break;
}
});
this.$get('/testplan/get-load-config/' + testId, (response) => {
if (response.data && response.data != "") {
let data = JSON.parse(response.data);
this.threadNumber = this.threadNumber || 10;
this.duration = this.duration || 30;
this.rampUpTime = this.rampUpTime || 12;
this.step = this.step || 3;
this.rpsLimit = this.rpsLimit || 10;
data.forEach(d => {
switch (d.key) {
case TARGET_LEVEL:
this.threadNumber = d.value;
break;
case RAMP_UP:
this.rampUpTime = d.value;
break;
case DURATION:
this.duration = d.value;
break;
case STEPS:
this.step = d.value;
break;
case RPS_LIMIT:
this.rpsLimit = d.value;
break;
default:
break;
}
});
this.calculateChart();
}
});
this.threadNumber = this.threadNumber || 10;
this.duration = this.duration || 30;
this.rampUpTime = this.rampUpTime || 12;
this.step = this.step || 3;
this.rpsLimit = this.rpsLimit || 10;
this.calculateChart();
}
});
}
},
calculateChart() {
if (this.duration < this.rampUpTime) {
this.rampUpTime = this.duration;
}
if (this.threadNumber < this.step) {
this.step = this.threadNumber;
if (this.rampUpTime < this.step) {
this.step = this.rampUpTime;
}
this.orgOptions = {
xAxis: {
type: 'category',
boundaryGap: false,
data: []
},
yAxis: {
@ -177,6 +183,7 @@
},
tooltip: {
trigger: 'axis',
formatter: '{a}: {c0}',
axisPointer: {
lineStyle: {
color: '#57617B'
@ -184,11 +191,11 @@
}
},
series: [{
name: 'User',
data: [],
type: 'line',
step: 'start',
smooth: false,
symbol: 'circle',
symbolSize: 5,
showSymbol: false,
lineStyle: {
@ -219,16 +226,23 @@
}]
};
let timePeriod = Math.floor(this.rampUpTime / this.step);
let threadPeriod = Math.floor(this.threadNumber / this.step);
let threadInc = threadPeriod;
let timeInc = timePeriod;
for (let i = 0; i < this.duration; i++) {
let threadPeriod = Math.floor(this.threadNumber / this.step);
let threadInc1 = Math.floor(this.threadNumber / this.step);
let threadInc2 = Math.ceil(this.threadNumber / this.step);
let inc2count = this.threadNumber - this.step * threadInc1;
for (let i = 0; i <= this.duration; i++) {
// x
this.orgOptions.xAxis.data.push(i);
if (i > timePeriod) {
threadPeriod = threadPeriod + threadInc;
timePeriod += timeInc;
if (inc2count > 0) {
threadPeriod = threadPeriod + threadInc2;
inc2count--;
} else {
threadPeriod = threadPeriod + threadInc1;
}
if (threadPeriod > this.threadNumber) {
threadPeriod = this.threadNumber;
}
@ -237,9 +251,6 @@
this.orgOptions.series[0].data.push(threadPeriod);
}
}
//
this.orgOptions.xAxis.data.push(this.duration);
this.orgOptions.series[0].data.push(this.threadNumber);
},
convertProperty() {
/// todo4jmeter ConcurrencyThreadGroup plugin