From aa542486730c17eeac2330a1aa9a88c56d7345fa Mon Sep 17 00:00:00 2001 From: shiziyuan9527 Date: Thu, 4 Mar 2021 17:41:30 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat(=E6=B5=8B=E8=AF=95=E8=AE=A1=E5=88=92):?= =?UTF-8?q?=20=E6=89=B9=E9=87=8F=E4=BF=AE=E6=94=B9=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E7=94=A8=E4=BE=8B=E6=89=A7=E8=A1=8C=E7=8E=AF=E5=A2=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/TestPlanApiCaseController.java | 6 ++ .../testcase/TestPlanApiCaseBatchRequest.java | 11 +++ .../track/service/TestPlanApiCaseService.java | 20 ++++- .../track/case/components/BatchEdit.vue | 41 ++++++++-- .../comonents/api/TestPlanApiCaseList.vue | 74 ++++++++++--------- 5 files changed, 109 insertions(+), 43 deletions(-) diff --git a/backend/src/main/java/io/metersphere/track/controller/TestPlanApiCaseController.java b/backend/src/main/java/io/metersphere/track/controller/TestPlanApiCaseController.java index 9039e564be..f3fcecf316 100644 --- a/backend/src/main/java/io/metersphere/track/controller/TestPlanApiCaseController.java +++ b/backend/src/main/java/io/metersphere/track/controller/TestPlanApiCaseController.java @@ -50,4 +50,10 @@ public class TestPlanApiCaseController { testPlanApiCaseService.deleteApiCaseBath(request); } + @PostMapping("/batch/update/env") + @RequiresRoles(value = {RoleConstants.TEST_USER, RoleConstants.TEST_MANAGER}, logical = Logical.OR) + public void batchUpdateEnv(@RequestBody TestPlanApiCaseBatchRequest request) { + testPlanApiCaseService.batchUpdateEnv(request); + } + } diff --git a/backend/src/main/java/io/metersphere/track/request/testcase/TestPlanApiCaseBatchRequest.java b/backend/src/main/java/io/metersphere/track/request/testcase/TestPlanApiCaseBatchRequest.java index 215e9cecfd..79ac65bda8 100644 --- a/backend/src/main/java/io/metersphere/track/request/testcase/TestPlanApiCaseBatchRequest.java +++ b/backend/src/main/java/io/metersphere/track/request/testcase/TestPlanApiCaseBatchRequest.java @@ -5,9 +5,20 @@ import lombok.Getter; import lombok.Setter; import java.util.List; +import java.util.Map; @Getter @Setter public class TestPlanApiCaseBatchRequest extends TestPlanTestCase { private List ids; + + /** + * 批量修改选中的数据 + */ + private Map selectRows; + + /** + * 项目ID,环境ID对应关系 + */ + private Map projectEnvMap; } diff --git a/backend/src/main/java/io/metersphere/track/service/TestPlanApiCaseService.java b/backend/src/main/java/io/metersphere/track/service/TestPlanApiCaseService.java index 17ae51df69..4f5ae37971 100644 --- a/backend/src/main/java/io/metersphere/track/service/TestPlanApiCaseService.java +++ b/backend/src/main/java/io/metersphere/track/service/TestPlanApiCaseService.java @@ -12,6 +12,7 @@ import io.metersphere.api.dto.definition.request.MsThreadGroup; import io.metersphere.api.service.ApiDefinitionExecResultService; import io.metersphere.api.service.ApiDefinitionService; import io.metersphere.api.service.ApiTestCaseService; +import io.metersphere.base.domain.ApiTestCaseExample; import io.metersphere.base.domain.ApiTestCaseWithBLOBs; import io.metersphere.base.domain.TestPlanApiCase; import io.metersphere.base.domain.TestPlanApiCaseExample; @@ -27,9 +28,7 @@ import org.springframework.util.CollectionUtils; import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; +import java.util.*; @Service @Transactional(rollbackFor = Exception.class) @@ -129,4 +128,19 @@ public class TestPlanApiCaseService { request.setIds(extTestPlanApiCaseMapper.getNotRelevanceCaseIds(planId, relevanceProjectIds)); deleteApiCaseBath(request); } + + public void batchUpdateEnv(TestPlanApiCaseBatchRequest request) { + // 批量修改用例环境 + Map rows = request.getSelectRows(); + Set ids = rows.keySet(); + Map env = request.getProjectEnvMap(); + if (env != null && !env.isEmpty()) { + ids.forEach(id -> { + TestPlanApiCase apiCase = new TestPlanApiCase(); + apiCase.setId(id); + apiCase.setEnvironmentId(env.get(rows.get(id))); + testPlanApiCaseMapper.updateByPrimaryKeySelective(apiCase); + }); + } + } } diff --git a/frontend/src/business/components/track/case/components/BatchEdit.vue b/frontend/src/business/components/track/case/components/BatchEdit.vue index 0fd6678255..421330b3ad 100644 --- a/frontend/src/business/components/track/case/components/BatchEdit.vue +++ b/frontend/src/business/components/track/case/components/BatchEdit.vue @@ -14,7 +14,11 @@ - + + + +
@@ -35,11 +39,13 @@ diff --git a/frontend/src/business/components/track/plan/view/comonents/api/TestPlanApiCaseList.vue b/frontend/src/business/components/track/plan/view/comonents/api/TestPlanApiCaseList.vue index 98ffc7eb4c..73073a8485 100644 --- a/frontend/src/business/components/track/plan/view/comonents/api/TestPlanApiCaseList.vue +++ b/frontend/src/business/components/track/plan/view/comonents/api/TestPlanApiCaseList.vue @@ -129,6 +129,10 @@ + + +
@@ -145,9 +149,9 @@ import ApiCaseList from "../../../../../api/definition/components/case/ApiCaseLi import MsContainer from "../../../../../common/components/MsContainer"; import MsBottomContainer from "../../../../../api/definition/components/BottomContainer"; import ShowMoreBtn from "../../../../case/components/ShowMoreBtn"; -import MsBatchEdit from "../../../../../api/definition/components/basis/BatchEdit"; +import BatchEdit from "@/business/components/track/case/components/BatchEdit"; import {API_METHOD_COLOUR, CASE_PRIORITY, RESULT_MAP} from "../../../../../api/definition/model/JsonData"; -import {getCurrentProjectID, getCurrentUser} from "@/common/js/utils"; +import {getCurrentProjectID, strMapToObj} from "@/common/js/utils"; import ApiListContainer from "../../../../../api/definition/components/list/ApiListContainer"; import PriorityTableItem from "../../../../common/tableItems/planview/PriorityTableItem"; import {getBodyUploadFiles, getUUID} from "../../../../../../../common/js/utils"; @@ -156,16 +160,17 @@ import MsRun from "../../../../../api/definition/components/Run"; import TestPlanApiCaseResult from "./TestPlanApiCaseResult"; import TestPlan from "../../../../../api/definition/components/jmeter/components/test-plan"; import ThreadGroup from "../../../../../api/definition/components/jmeter/components/thread-group"; -import {TEST_CASE_LIST, TEST_PLAN_API_CASE, WORKSPACE_ID} from "@/common/js/constants"; +import {TEST_PLAN_API_CASE, WORKSPACE_ID} from "@/common/js/constants"; import {_filter, _sort, getLabel} from "@/common/js/tableUtils"; import HeaderCustom from "@/business/components/common/head/HeaderCustom"; -import {Test_Plan_Api_Case, Track_Test_Case} from "@/business/components/common/model/JsonData"; +import {Test_Plan_Api_Case} from "@/business/components/common/model/JsonData"; import HeaderLabelOperate from "@/business/components/common/head/HeaderLabelOperate"; export default { name: "TestPlanApiCaseList", components: { + BatchEdit, HeaderLabelOperate, HeaderCustom, TestPlanApiCaseResult, @@ -182,7 +187,6 @@ export default { MsContainer, MsBottomContainer, ShowMoreBtn, - MsBatchEdit }, data() { return { @@ -198,10 +202,11 @@ export default { selectRows: new Set(), buttons: [ {name: this.$t('test_track.case.batch_unlink'), handleClick: this.handleDeleteBatch}, - {name: this.$t('api_test.automation.batch_execute'), handleClick: this.handleBatchExecute} + {name: this.$t('api_test.automation.batch_execute'), handleClick: this.handleBatchExecute}, + {name: this.$t('test_track.case.batch_edit_case'), handleClick: this.handleBatchEdit} ], typeArr: [ - {id: 'priority', name: this.$t('test_track.case.priority')}, + {id: 'projectEnv', name: this.$t('api_test.definition.request.run_env')}, ], priorityFilters: [ {text: 'P0', value: 'P0'}, @@ -212,6 +217,7 @@ export default { valueArr: { priority: CASE_PRIORITY, userId: [], + projectEnv: [] }, methodColorMap: new Map(API_METHOD_COLOUR), tableData: [], @@ -225,7 +231,9 @@ export default { reportId: "", response: {}, rowLoading: "", - userFilters: [] + userFilters: [], + projectIds: [], + projectList: [] } }, props: { @@ -342,15 +350,6 @@ export default { this.$set(row, "showMore", true); this.selectRows.add(row); } - let arr = Array.from(this.selectRows); - // 选中1个以上的用例时显示更多操作 - if (this.selectRows.size === 1) { - this.$set(arr[0], "showMore", false); - } else if (this.selectRows.size === 2) { - arr.forEach(row => { - this.$set(row, "showMore", true); - }) - } }, showExecResult(row) { this.$emit('showExecResult', row); @@ -369,16 +368,10 @@ export default { }, handleSelectAll(selection) { if (selection.length > 0) { - if (selection.length === 1) { - selection.hashTree = []; - this.selectRows.add(selection[0]); - } else { - this.tableData.forEach(item => { - item.hashTree = []; - this.$set(item, "showMore", true); - this.selectRows.add(item); - }); - } + this.tableData.forEach(item => { + this.$set(item, "showMore", true); + this.selectRows.add(item); + }); } else { this.selectRows.clear(); this.tableData.forEach(row => { @@ -449,16 +442,27 @@ export default { this.reportId = getUUID().substring(0, 8); }); }, + handleBatchEdit() { + this.$refs.batchEdit.open(this.selectRows.size); + this.$refs.batchEdit.setSelectRows(this.selectRows); + }, batchEdit(form) { - let arr = Array.from(this.selectRows); - let ids = arr.map(row => row.id); let param = {}; - param[form.type] = form.value; - param.ids = ids; - this.$post('/api/testcase/batch/edit', param, () => { - this.$success(this.$t('commons.save_success')); - this.initTable(); - }); + // 批量修改环境 + if (form.type === 'projectEnv') { + let map = new Map(); + param.projectEnvMap = strMapToObj(form.projectEnvMap); + this.selectRows.forEach(row => { + map[row.id] = row.projectId; + }) + param.selectRows = map; + this.$post('/test/plan/api/case/batch/update/env', param, () => { + this.$success(this.$t('commons.save_success')); + this.initTable(); + }); + } else { + // 批量修改其它 + } }, handleBatchExecute() { this.selectRows.forEach(row => { From 41cb3e0283b09c10a6bbbfb7e1b7f839d05b0dff Mon Sep 17 00:00:00 2001 From: "song.tianyang" Date: Thu, 4 Mar 2021 19:21:18 +0800 Subject: [PATCH 2/2] =?UTF-8?q?feat:=20=E8=AE=BE=E7=BD=AE=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=EF=BC=8C=E7=B3=BB=E7=BB=9F->=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E3=80=81=E7=BB=84=E7=BB=87->=E6=88=90=E5=91=98=20=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E6=89=B9=E9=87=8F=E5=A4=84=E7=90=86=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E3=80=90=E6=B7=BB=E5=8A=A0=E5=B7=A5=E4=BD=9C=E7=A9=BA=E9=97=B4?= =?UTF-8?q?=E3=80=91=E3=80=90=E6=B7=BB=E5=8A=A0=E8=A7=92=E8=89=B2=E3=80=91?= =?UTF-8?q?=EF=BC=9B=E5=B7=A5=E4=BD=9C=E7=A9=BA=E9=97=B4->=E6=88=90?= =?UTF-8?q?=E5=91=98=20=E6=96=B0=E5=A2=9E=E6=89=B9=E9=87=8F=E5=A4=84?= =?UTF-8?q?=E7=90=86=E5=8A=9F=E8=83=BD=E3=80=90=E6=B7=BB=E5=8A=A0=E8=A7=92?= =?UTF-8?q?=E8=89=B2=E3=80=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 设置模块,系统->用户、组织->成员 新增批量处理功能【添加工作空间】【添加角色】;工作空间->成员 新增批量处理功能【添加角色】 --- .../mapper/ext/ExtOrganizationMapper.java | 2 +- .../base/mapper/ext/ExtOrganizationMapper.xml | 7 +- .../base/mapper/ext/ExtUserMapper.java | 4 + .../base/mapper/ext/ExtUserMapper.xml | 26 +++ .../base/mapper/ext/ExtUserRoleMapper.java | 3 + .../base/mapper/ext/ExtUserRoleMapper.xml | 18 ++ .../base/mapper/ext/ExtWorkspaceMapper.java | 4 +- .../base/mapper/ext/ExtWorkspaceMapper.xml | 14 +- .../constants/BatchProcessUserInfoType.java | 5 + .../controller/UserController.java | 60 ++++++- .../controller/request/UserRequest.java | 5 + .../resourcepool/UserBatchProcessRequest.java | 25 +++ .../java/io/metersphere/dto/CascaderDTO.java | 22 +++ .../io/metersphere/dto/CascaderParse.java | 108 ++++++++++++ .../excel/listener/UserDataListener.java | 13 +- .../service/OrganizationService.java | 4 +- .../io/metersphere/service/UserService.java | 161 ++++++++++++++++-- .../metersphere/service/WorkspaceService.java | 8 +- .../main/resources/i18n/messages.properties | 5 + .../resources/i18n/messages_en_US.properties | 5 + .../resources/i18n/messages_zh_CN.properties | 5 + .../resources/i18n/messages_zh_TW.properties | 5 + .../organization/OrganizationMember.vue | 124 +++++++++++++- .../components/settings/system/User.vue | 134 ++++++++++++++- .../system/components/UserCascader.vue | 124 ++++++++++++++ .../settings/workspace/WorkspaceMember.vue | 113 +++++++++++- frontend/src/i18n/en-US.js | 7 +- frontend/src/i18n/zh-CN.js | 7 +- frontend/src/i18n/zh-TW.js | 7 +- 29 files changed, 972 insertions(+), 53 deletions(-) create mode 100644 backend/src/main/java/io/metersphere/commons/constants/BatchProcessUserInfoType.java create mode 100644 backend/src/main/java/io/metersphere/controller/request/resourcepool/UserBatchProcessRequest.java create mode 100644 backend/src/main/java/io/metersphere/dto/CascaderDTO.java create mode 100644 backend/src/main/java/io/metersphere/dto/CascaderParse.java create mode 100644 frontend/src/business/components/settings/system/components/UserCascader.vue diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtOrganizationMapper.java b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtOrganizationMapper.java index aab424dadb..7b41d74c83 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtOrganizationMapper.java +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtOrganizationMapper.java @@ -10,5 +10,5 @@ public interface ExtOrganizationMapper { int checkSourceRole(@Param("sourceId") String sourceId,@Param("userId") String userId,@Param("roleId") String roleId); - List findAllIdAndName(); + List findIdAndNameByOrganizationId(@Param("organizationId")String organizationID); } diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtOrganizationMapper.xml b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtOrganizationMapper.xml index c968753e01..0f19f718d5 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtOrganizationMapper.xml +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtOrganizationMapper.xml @@ -11,7 +11,12 @@ - select id,name from Organization + + + AND id = #{organizationId} + + \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtUserMapper.java b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtUserMapper.java index 0fbc3044fc..78d867943e 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtUserMapper.java +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtUserMapper.java @@ -1,7 +1,9 @@ package io.metersphere.base.mapper.ext; +import io.metersphere.api.dto.automation.ApiScenarioRequest; import io.metersphere.base.domain.User; import io.metersphere.controller.request.UserRequest; +import io.metersphere.controller.request.resourcepool.UserBatchProcessRequest; import io.metersphere.notice.domain.UserDetail; import org.apache.ibatis.annotations.MapKey; import org.apache.ibatis.annotations.Param; @@ -25,4 +27,6 @@ public interface ExtUserMapper { Map queryNameByIds(List userIds); List selectAllId(); + + List selectIdsByQuery(@Param("request") UserRequest request); } diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtUserMapper.xml b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtUserMapper.xml index 825e901dd4..11325107cc 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtUserMapper.xml +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtUserMapper.xml @@ -16,6 +16,27 @@ + + + + AND user.id like CONCAT('%', #{request.id},'%') + + + AND user.name like CONCAT('%', #{request.name},'%') + + + AND user.email like CONCAT('%', #{request.email},'%') + + + + AND user.id not in + + #{itemId} + + + + + + \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtUserRoleMapper.java b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtUserRoleMapper.java index f504f373c0..1480191a25 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtUserRoleMapper.java +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtUserRoleMapper.java @@ -2,6 +2,7 @@ package io.metersphere.base.mapper.ext; import io.metersphere.base.domain.Role; import io.metersphere.base.domain.User; +import io.metersphere.controller.request.UserRequest; import io.metersphere.controller.request.member.QueryMemberRequest; import io.metersphere.controller.request.organization.QueryOrgMemberRequest; import io.metersphere.dto.OrganizationMemberDTO; @@ -28,4 +29,6 @@ public interface ExtUserRoleMapper { List getTestManagerAndTestUserList(@Param("request") QueryMemberRequest request); + + List selectIdsByQuery(@Param("organizationId") String organizationId, @Param("orgMember")UserRequest condition); } diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtUserRoleMapper.xml b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtUserRoleMapper.xml index 3a53df48e8..a68df1b56c 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtUserRoleMapper.xml +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtUserRoleMapper.xml @@ -108,4 +108,22 @@ order by user_role.update_time desc) temp + + \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtWorkspaceMapper.java b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtWorkspaceMapper.java index dbdede5cf3..50d993bc1f 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtWorkspaceMapper.java +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtWorkspaceMapper.java @@ -11,5 +11,7 @@ public interface ExtWorkspaceMapper { List getWorkspaceWithOrg(@Param("request") WorkspaceRequest request); List getWorkspaceIdsByOrgId(@Param("orgId") String orgId); - List findAllIdAndName(); + String getOrganizationIdById(String resourceID); + + List findIdAndNameByOrganizationId(@Param("organizationId") String organizationId); } diff --git a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtWorkspaceMapper.xml b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtWorkspaceMapper.xml index fc65c59d02..1932780950 100644 --- a/backend/src/main/java/io/metersphere/base/mapper/ext/ExtWorkspaceMapper.xml +++ b/backend/src/main/java/io/metersphere/base/mapper/ext/ExtWorkspaceMapper.xml @@ -18,8 +18,18 @@ where organization_id = #{orgId} - + select id,name,organization_id AS organizationId from workspace + + + AND organization_id = #{organizationId} + + + + + \ No newline at end of file diff --git a/backend/src/main/java/io/metersphere/commons/constants/BatchProcessUserInfoType.java b/backend/src/main/java/io/metersphere/commons/constants/BatchProcessUserInfoType.java new file mode 100644 index 0000000000..11798c100a --- /dev/null +++ b/backend/src/main/java/io/metersphere/commons/constants/BatchProcessUserInfoType.java @@ -0,0 +1,5 @@ +package io.metersphere.commons.constants; + +public enum BatchProcessUserInfoType { + ADD_WORKSPACE,ADD_USER_ROLE +} diff --git a/backend/src/main/java/io/metersphere/controller/UserController.java b/backend/src/main/java/io/metersphere/controller/UserController.java index 78507c2432..51c43eb0ee 100644 --- a/backend/src/main/java/io/metersphere/controller/UserController.java +++ b/backend/src/main/java/io/metersphere/controller/UserController.java @@ -1,8 +1,11 @@ package io.metersphere.controller; +import com.alibaba.fastjson.JSONObject; import com.github.pagehelper.Page; import com.github.pagehelper.PageHelper; +import io.metersphere.base.domain.Organization; import io.metersphere.base.domain.User; +import io.metersphere.base.domain.Workspace; import io.metersphere.commons.constants.RoleConstants; import io.metersphere.commons.exception.MSException; import io.metersphere.commons.user.SessionUser; @@ -15,8 +18,8 @@ import io.metersphere.controller.request.member.QueryMemberRequest; import io.metersphere.controller.request.member.UserRequest; import io.metersphere.controller.request.organization.AddOrgMemberRequest; import io.metersphere.controller.request.organization.QueryOrgMemberRequest; -import io.metersphere.dto.UserDTO; -import io.metersphere.dto.UserRoleDTO; +import io.metersphere.controller.request.resourcepool.UserBatchProcessRequest; +import io.metersphere.dto.*; import io.metersphere.excel.domain.ExcelResponse; import io.metersphere.i18n.Translator; import io.metersphere.service.CheckPermissionService; @@ -26,12 +29,16 @@ import io.metersphere.service.WorkspaceService; import org.apache.commons.lang3.StringUtils; import org.apache.shiro.authz.annotation.Logical; import org.apache.shiro.authz.annotation.RequiresRoles; +import org.checkerframework.checker.units.qual.C; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; import javax.servlet.http.HttpServletResponse; +import java.util.ArrayList; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; @RequestMapping("user") @RestController @@ -315,4 +322,53 @@ public class UserController { public ExcelResponse testCaseImport(MultipartFile file, @PathVariable String userId) { return userService.userImport(file, userId); } + + @PostMapping("/special/batchProcessUserInfo") + @RequiresRoles(value = {RoleConstants.ADMIN, RoleConstants.ORG_ADMIN,RoleConstants.TEST_MANAGER}) + public String batchProcessUserInfo(@RequestBody UserBatchProcessRequest request) { + String returnString = "success"; + userService.batchProcessUserInfo(request); + return returnString; + } + + @GetMapping("/getWorkspaceDataStruct/{organizationId}") + public List getWorkspaceDataStruct(@PathVariable String organizationId) { + List organizationList = organizationService.findIdAndNameByOrganizationId(organizationId); + List workspaceDTOList = workspaceService.findIdAndNameByOrganizationId(organizationId); + if(!workspaceDTOList.isEmpty()){ + Map> orgIdWorkspaceMap = workspaceDTOList.stream().collect(Collectors.groupingBy(WorkspaceDTO::getOrganizationId)); + List returnList = CascaderParse.parseWorkspaceDataStruct(organizationList,orgIdWorkspaceMap); + return returnList; + }else { + return new ArrayList<>(); + } + } + + @GetMapping("/getUserRoleDataStruct/{organizationId}") + public List getUserRoleDataStruct(@PathVariable String organizationId) { + List organizationList = organizationService.findIdAndNameByOrganizationId(organizationId); + List workspaceDTOList = workspaceService.findIdAndNameByOrganizationId(organizationId); + if(!workspaceDTOList.isEmpty()){ + Map> orgIdWorkspaceMap = workspaceDTOList.stream().collect(Collectors.groupingBy(WorkspaceDTO::getOrganizationId)); + List returnList = CascaderParse.parseUserRoleDataStruct(organizationList,orgIdWorkspaceMap,false); + return returnList; + }else { + return new ArrayList<>(); + } + } + + @GetMapping("/getWorkspaceUserRoleDataStruct/{organizationId}") + public List getWorkspaceUserRoleDataStruct(@PathVariable String organizationId) { + List organizationList = organizationService.findIdAndNameByOrganizationId(organizationId); + List workspaceDTOList = workspaceService.findIdAndNameByOrganizationId(organizationId); + if(!workspaceDTOList.isEmpty()){ + Map> orgIdWorkspaceMap = workspaceDTOList.stream().collect(Collectors.groupingBy(WorkspaceDTO::getOrganizationId)); + List returnList = CascaderParse.parseUserRoleDataStruct(organizationList,orgIdWorkspaceMap,true); + return returnList; + }else { + return new ArrayList<>(); + } + } + + } diff --git a/backend/src/main/java/io/metersphere/controller/request/UserRequest.java b/backend/src/main/java/io/metersphere/controller/request/UserRequest.java index 37808b1f4c..dc03e2fad2 100644 --- a/backend/src/main/java/io/metersphere/controller/request/UserRequest.java +++ b/backend/src/main/java/io/metersphere/controller/request/UserRequest.java @@ -3,10 +3,15 @@ package io.metersphere.controller.request; import lombok.Getter; import lombok.Setter; +import java.util.List; + @Getter @Setter public class UserRequest { private String id; private String name; private String email; + + boolean selectAll; + List unSelectIds; } diff --git a/backend/src/main/java/io/metersphere/controller/request/resourcepool/UserBatchProcessRequest.java b/backend/src/main/java/io/metersphere/controller/request/resourcepool/UserBatchProcessRequest.java new file mode 100644 index 0000000000..1b69ab71d1 --- /dev/null +++ b/backend/src/main/java/io/metersphere/controller/request/resourcepool/UserBatchProcessRequest.java @@ -0,0 +1,25 @@ +package io.metersphere.controller.request.resourcepool; + +import io.metersphere.controller.request.UserRequest; +import lombok.Getter; +import lombok.Setter; + +import java.util.List; +import java.util.concurrent.locks.Condition; + +/** + * @author song.tianyang + * @Date 2021/3/3 5:21 下午 + * @Description + */ +@Getter +@Setter +public class UserBatchProcessRequest { + List ids; + String projectId; + String batchType; + List batchProcessValue; + String organizationId; + UserRequest condition; +} + diff --git a/backend/src/main/java/io/metersphere/dto/CascaderDTO.java b/backend/src/main/java/io/metersphere/dto/CascaderDTO.java new file mode 100644 index 0000000000..ebba05be5c --- /dev/null +++ b/backend/src/main/java/io/metersphere/dto/CascaderDTO.java @@ -0,0 +1,22 @@ +package io.metersphere.dto; + +import lombok.Getter; +import lombok.Setter; + +import java.util.ArrayList; +import java.util.List; + +/** + * 级联选择器-数据格式 + * + * @author song.tianyang + * @Date 2021/3/4 10:47 上午 + * @Description + */ +@Getter +@Setter +public class CascaderDTO { + private String value; + private String label; + private List children; +} diff --git a/backend/src/main/java/io/metersphere/dto/CascaderParse.java b/backend/src/main/java/io/metersphere/dto/CascaderParse.java new file mode 100644 index 0000000000..b95b15d0a0 --- /dev/null +++ b/backend/src/main/java/io/metersphere/dto/CascaderParse.java @@ -0,0 +1,108 @@ +package io.metersphere.dto; + +import bsh.StringUtil; +import io.metersphere.commons.constants.RoleConstants; +import io.metersphere.i18n.Translator; +import org.apache.commons.lang3.StringUtils; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +/** + * @author song.tianyang + * @Date 2021/3/4 2:47 下午 + * @Description + */ +public class CascaderParse { + public static List parseUserRoleDataStruct(List organizationList, Map> orgIdWorkspaceMap,boolean hideOrgRole) { + List returnList = new ArrayList<>(); + for (OrganizationMemberDTO orgDTO : organizationList) { + String orgId = orgDTO.getId(); + List workspaceDTOList = orgIdWorkspaceMap.get(orgId); + CascaderDTO orgCascader = generateCascaderDTO(orgDTO.getId(), orgDTO.getName(), null); + if (workspaceDTOList != null) { + List children = new ArrayList<>(); + for (WorkspaceDTO workspace : workspaceDTOList) { + String parentCascaderType = "workspace"; + if(hideOrgRole){ + parentCascaderType = "hideOrg"; + } + List cascaderDTOList = getUserRoleCascaderDTO(parentCascaderType, workspace.getId()); + CascaderDTO workspaceCascader = generateCascaderDTO(workspace.getId(), workspace.getName(), cascaderDTOList); + children.add(workspaceCascader); + } + orgCascader.setChildren(children); + } else { + List cascaderDTOList = getUserRoleCascaderDTO("org", orgDTO.getId()); + orgCascader.setChildren(cascaderDTOList); + } + returnList.add(orgCascader); + } + return returnList; + } + + private static List getUserRoleCascaderDTO(String parentCascaderType, String parentID) { + String idPrefix = ""; + String idSuffix = "<->" + parentCascaderType; + if (!StringUtils.isEmpty(parentID)) { + idPrefix = parentID + "<->"; + } + CascaderDTO orgAdminCascasder = generateCascaderDTO(idPrefix + RoleConstants.ORG_ADMIN + idSuffix, Translator.get("org_admin"), null); + CascaderDTO orgMemberCascasder = generateCascaderDTO(idPrefix + RoleConstants.ORG_MEMBER + idSuffix, Translator.get("org_member"), null); + CascaderDTO testManagerCascasder = generateCascaderDTO(idPrefix + RoleConstants.TEST_MANAGER + idSuffix, Translator.get("test_manager"), null); + CascaderDTO testerCascasder = generateCascaderDTO(idPrefix + RoleConstants.TEST_USER + idSuffix, Translator.get("tester"), null); + CascaderDTO readOnlyUserCascasder = generateCascaderDTO(idPrefix + RoleConstants.TEST_VIEWER + idSuffix, Translator.get("read_only_user"), null); + + //默认都要添加这两种类型 + List returnList = new ArrayList<>(); + + switch (parentCascaderType) { + case "workspace": + returnList.add(orgAdminCascasder); + returnList.add(orgMemberCascasder); + returnList.add(testManagerCascasder); + returnList.add(testerCascasder); + returnList.add(readOnlyUserCascasder); + break; + case "org": + returnList.add(orgAdminCascasder); + returnList.add(orgMemberCascasder); + break; + case "hideOrg": + returnList.add(testManagerCascasder); + returnList.add(testerCascasder); + returnList.add(readOnlyUserCascasder); + break; + } + return returnList; + } + + public static List parseWorkspaceDataStruct(List organizationList, Map> orgIdWorkspaceMap) { + List returnList = new ArrayList<>(); + for (OrganizationMemberDTO orgDTO : organizationList) { + String orgId = orgDTO.getId(); + List workspaceDTOList = orgIdWorkspaceMap.get(orgId); + if (workspaceDTOList != null) { + List children = new ArrayList<>(); + for (WorkspaceDTO workspace : workspaceDTOList) { + CascaderDTO workspaceCascader = generateCascaderDTO(workspace.getId(), workspace.getName(), null); + children.add(workspaceCascader); + } + CascaderDTO orgCascader = generateCascaderDTO(orgDTO.getId(), orgDTO.getName(), children); + returnList.add(orgCascader); + } + } + return returnList; + } + + private static CascaderDTO generateCascaderDTO(String value, String lable, List children) { + CascaderDTO cascaderDTO = new CascaderDTO(); + cascaderDTO.setLabel(lable); + cascaderDTO.setValue(value); + if (children != null && !children.isEmpty()) { + cascaderDTO.setChildren(children); + } + return cascaderDTO; + } +} diff --git a/backend/src/main/java/io/metersphere/excel/listener/UserDataListener.java b/backend/src/main/java/io/metersphere/excel/listener/UserDataListener.java index 7f5f484c1c..eddf3f3bc7 100644 --- a/backend/src/main/java/io/metersphere/excel/listener/UserDataListener.java +++ b/backend/src/main/java/io/metersphere/excel/listener/UserDataListener.java @@ -1,5 +1,6 @@ package io.metersphere.excel.listener; +import io.metersphere.commons.constants.RoleConstants; import io.metersphere.commons.exception.MSException; import io.metersphere.commons.utils.CommonBeanFactory; import io.metersphere.controller.request.member.UserRequest; @@ -176,37 +177,37 @@ public class UserDataListener extends EasyExcelListener { if (StringUtils.equalsIgnoreCase(Translator.get("options_yes"), data.getUserIsAdmin())) { List adminIdList = new ArrayList<>(); adminIdList.add("adminSourceId"); - Map adminRoleMap = this.genRoleMap("admin", adminIdList); + Map adminRoleMap = this.genRoleMap(RoleConstants.ADMIN, adminIdList); roleMapList.add(adminRoleMap); } //判断组织管理员 List orgManagerOrdIdList = this.getIdByExcelInfoAndIdDic(data.getUserIsOrgAdmin(), data.getOrgAdminOrganization(), orgNameMap); if (!orgManagerOrdIdList.isEmpty()) { - Map orgAdminRoleMap = this.genRoleMap("org_admin", orgManagerOrdIdList); + Map orgAdminRoleMap = this.genRoleMap(RoleConstants.ORG_ADMIN, orgManagerOrdIdList); roleMapList.add(orgAdminRoleMap); } //判断组织成员 List orgMemberOrdIdList = this.getIdByExcelInfoAndIdDic(data.getUserIsOrgMember(), data.getOrgMemberOrganization(), orgNameMap); if (!orgMemberOrdIdList.isEmpty()) { - Map orgMemberRoleMap = this.genRoleMap("org_member", orgMemberOrdIdList); + Map orgMemberRoleMap = this.genRoleMap(RoleConstants.ORG_MEMBER, orgMemberOrdIdList); roleMapList.add(orgMemberRoleMap); } //判断测试经理 List testManagerWorkspaceIdList = this.getIdByExcelInfoAndIdDic(data.getUserIsTestManager(), data.getTestManagerWorkspace(), workspaceNameMap); if (!testManagerWorkspaceIdList.isEmpty()) { - Map testManagerRoleMap = this.genRoleMap("test_manager", testManagerWorkspaceIdList); + Map testManagerRoleMap = this.genRoleMap(RoleConstants.TEST_MANAGER, testManagerWorkspaceIdList); roleMapList.add(testManagerRoleMap); } //判断测试人员 List testgerWorkspaceIdList = this.getIdByExcelInfoAndIdDic(data.getUserIsTester(), data.getTesterWorkspace(), workspaceNameMap); if (!testgerWorkspaceIdList.isEmpty()) { - Map testerRoleMap = this.genRoleMap("test_user", testgerWorkspaceIdList); + Map testerRoleMap = this.genRoleMap(RoleConstants.TEST_USER, testgerWorkspaceIdList); roleMapList.add(testerRoleMap); } //判断只读用户 List viewerWorkspaceIdList = this.getIdByExcelInfoAndIdDic(data.getUserIsViewer(), data.getViewerWorkspace(), workspaceNameMap); if (!viewerWorkspaceIdList.isEmpty()) { - Map testViewerRoleMap = this.genRoleMap("test_viewer", viewerWorkspaceIdList); + Map testViewerRoleMap = this.genRoleMap(RoleConstants.TEST_VIEWER, viewerWorkspaceIdList); roleMapList.add(testViewerRoleMap); } request.setRoles(roleMapList); diff --git a/backend/src/main/java/io/metersphere/service/OrganizationService.java b/backend/src/main/java/io/metersphere/service/OrganizationService.java index 5ce5fdcab2..70a6d2cd26 100644 --- a/backend/src/main/java/io/metersphere/service/OrganizationService.java +++ b/backend/src/main/java/io/metersphere/service/OrganizationService.java @@ -179,7 +179,7 @@ public class OrganizationService { } } - public List findAllIdAndName(){ - return extOrganizationMapper.findAllIdAndName(); + public List findIdAndNameByOrganizationId(String OrganizationID){ + return extOrganizationMapper.findIdAndNameByOrganizationId(OrganizationID); } } diff --git a/backend/src/main/java/io/metersphere/service/UserService.java b/backend/src/main/java/io/metersphere/service/UserService.java index cc09358f1e..d151a8df46 100644 --- a/backend/src/main/java/io/metersphere/service/UserService.java +++ b/backend/src/main/java/io/metersphere/service/UserService.java @@ -1,21 +1,16 @@ package io.metersphere.service; import com.alibaba.excel.EasyExcelFactory; +import io.metersphere.api.dto.automation.ApiScenarioRequest; import io.metersphere.base.domain.*; import io.metersphere.base.mapper.*; import io.metersphere.base.mapper.ext.ExtOrganizationMapper; import io.metersphere.base.mapper.ext.ExtUserMapper; import io.metersphere.base.mapper.ext.ExtUserRoleMapper; -import io.metersphere.commons.constants.RoleConstants; -import io.metersphere.commons.constants.TestCaseConstants; -import io.metersphere.commons.constants.UserSource; -import io.metersphere.commons.constants.UserStatus; +import io.metersphere.commons.constants.*; import io.metersphere.commons.exception.MSException; import io.metersphere.commons.user.SessionUser; -import io.metersphere.commons.utils.CodingUtil; -import io.metersphere.commons.utils.CommonBeanFactory; -import io.metersphere.commons.utils.LogUtil; -import io.metersphere.commons.utils.SessionUtils; +import io.metersphere.commons.utils.*; import io.metersphere.controller.ResultHolder; import io.metersphere.controller.request.LoginRequest; import io.metersphere.controller.request.member.AddMemberRequest; @@ -24,6 +19,7 @@ import io.metersphere.controller.request.member.QueryMemberRequest; import io.metersphere.controller.request.member.UserRequest; import io.metersphere.controller.request.organization.AddOrgMemberRequest; import io.metersphere.controller.request.organization.QueryOrgMemberRequest; +import io.metersphere.controller.request.resourcepool.UserBatchProcessRequest; import io.metersphere.dto.OrganizationMemberDTO; import io.metersphere.dto.UserDTO; import io.metersphere.dto.UserRoleDTO; @@ -43,6 +39,7 @@ import org.apache.shiro.SecurityUtils; import org.apache.shiro.authc.*; import org.apache.shiro.authz.UnauthorizedException; import org.apache.shiro.subject.Subject; +import org.python.antlr.ast.Str; import org.springframework.beans.BeanUtils; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; @@ -313,7 +310,6 @@ public class UserService { userRoleExample.createCriteria().andUserIdEqualTo(userId); List userRoles = userRoleMapper.selectByExample(userRoleExample); List list = userRoles.stream().map(UserRole::getSourceId).collect(Collectors.toList()); - if (!CollectionUtils.isEmpty(list)) { if (list.contains(user.getLastWorkspaceId()) || list.contains(user.getLastOrganizationId())) { user.setLastOrganizationId(""); @@ -335,7 +331,6 @@ public class UserService { if (userMapper.countByExample(example) > 0) { MSException.throwException(Translator.get("user_email_already_exists")); } - user.setUpdateTime(System.currentTimeMillis()); userMapper.updateByPrimaryKeySelective(user); } @@ -651,7 +646,7 @@ public class UserService { SessionUser user = SessionUtils.getUser(); for (int i = 1; i <= 2; i++) { UserExcelData data = new UserExcelData(); - data.setId("user_id_"+i); + data.setId("user_id_" + i); data.setName(Translator.get("user") + i); String workspace = ""; for (int workspaceIndex = 1; workspaceIndex <= i; workspaceIndex++) { @@ -690,18 +685,18 @@ public class UserService { try { Class clazz = new UserExcelDataFactory().getExcelDataByLocal(); - Map orgNameMap = new HashMap<>(); - Map workspaceNameMap = new HashMap<>(); + Map orgNameMap = new HashMap<>(); + Map workspaceNameMap = new HashMap<>(); - List organizationList = extOrganizationMapper.findAllIdAndName(); + List organizationList = extOrganizationMapper.findIdAndNameByOrganizationId("All"); for (OrganizationMemberDTO model : organizationList) { - orgNameMap.put(model.getName(),model.getId()); + orgNameMap.put(model.getName(), model.getId()); } - List workspaceList = workspaceService.findAllIdAndName(); + List workspaceList = workspaceService.findIdAndNameByOrganizationId("All"); for (WorkspaceDTO model : workspaceList) { - workspaceNameMap.put(model.getName(),model.getId()); + workspaceNameMap.put(model.getName(), model.getId()); } - EasyExcelListener easyExcelListener = new UserDataListener(clazz,workspaceNameMap,orgNameMap); + EasyExcelListener easyExcelListener = new UserDataListener(clazz, workspaceNameMap, orgNameMap); EasyExcelFactory.read(multipartFile.getInputStream(), clazz, easyExcelListener).sheet().doRead(); errList = easyExcelListener.getErrList(); } catch (Exception e) { @@ -722,4 +717,134 @@ public class UserService { public List selectAllId() { return extUserMapper.selectAllId(); } + + /** + * 批量处理用户信息: + * 添加用户到工作空间; + * 添加用户权限 + * + * @param request + */ + public void batchProcessUserInfo(UserBatchProcessRequest request) { + List userIdList = this.selectIdByUserRequest(request); + String batchType = request.getBatchType(); + for (String userID : userIdList) { + Map> roleResourceIdMap = new HashMap<>(); + if (StringUtils.equals(BatchProcessUserInfoType.ADD_WORKSPACE.name(), batchType)) { + //添加工作空间时,默认赋予只读用户权限 + String userRole = RoleConstants.TEST_VIEWER; + List workspaceID = request.getBatchProcessValue(); + if (workspaceID != null && !workspaceID.isEmpty()) { + roleResourceIdMap.put(userRole, workspaceID); + } + } else if (StringUtils.equals(BatchProcessUserInfoType.ADD_USER_ROLE.name(), batchType)) { + roleResourceIdMap = this.genRoleResourceMap(request.getBatchProcessValue()); + } + if (!roleResourceIdMap.isEmpty()) { + UserRoleExample userRoleExample = new UserRoleExample(); + userRoleExample.createCriteria().andUserIdEqualTo(userID); + List userRoles = userRoleMapper.selectByExample(userRoleExample); + UserRequest user = this.convert2UserRequest(userID, roleResourceIdMap, userRoles); + this.addUserWorkspaceAndRole(user, userRoles); + } + } + } + + private List selectIdByUserRequest(UserBatchProcessRequest request) { + + if (request.getCondition() != null && request.getCondition().isSelectAll()) { + List userIdList = new ArrayList<>(); + if(StringUtils.isEmpty(request.getOrganizationId())){ + userIdList = extUserMapper.selectIdsByQuery(request.getCondition()); + }else{ + //组织->成员 页面发起的请求 + userIdList = extUserRoleMapper.selectIdsByQuery(request.getOrganizationId(),request.getCondition()); + } + + return userIdList; + } else { + return request.getIds(); + } + + } + + private Map> genRoleResourceMap(List batchProcessValue) { + Map> returnMap = new HashMap<>(); + Map workspaceToOrgMap = new HashMap<>(); + for (String string : batchProcessValue) { + String[] stringArr = string.split("<->"); + // string格式: 资源ID<->权限<->workspace/org + if (stringArr.length == 3) { + String resourceID = stringArr[0]; + String role = stringArr[1]; + String sourceType = stringArr[2]; + String finalResourceId = resourceID; + if (StringUtils.equalsIgnoreCase(sourceType, "workspace")) { + if (StringUtils.equalsAnyIgnoreCase(role, RoleConstants.ORG_ADMIN, RoleConstants.ORG_MEMBER)) { + finalResourceId = workspaceToOrgMap.get(resourceID); + if (finalResourceId == null) { + finalResourceId = workspaceService.getOrganizationIdById(resourceID); + workspaceToOrgMap.put(resourceID, finalResourceId); + } + } + } + if (StringUtils.isNotEmpty(finalResourceId)) { + if (returnMap.containsKey(role)) { + if (!returnMap.get(role).contains(finalResourceId)) { + returnMap.get(role).add(finalResourceId); + } + + } else { + List list = new ArrayList<>(); + list.add(finalResourceId); + returnMap.put(role, list); + } + } + } + } + return returnMap; + } + + private UserRequest convert2UserRequest(String userID, Map> roleIdMap, List userRoles) { + Map> userRoleAndResourceMap = userRoles.stream().collect( + Collectors.groupingBy(UserRole::getRoleId, Collectors.mapping(UserRole::getSourceId, Collectors.toList()))); + List> roles = new ArrayList<>(); + for (Map.Entry> entry : roleIdMap.entrySet()) { + String role = entry.getKey(); + List rawResourceIDList = entry.getValue(); + List resourceIDList = new ArrayList<>(); + for (String resourceID : rawResourceIDList) { + if (userRoleAndResourceMap.containsKey(role) && userRoleAndResourceMap.get(role).contains(resourceID)) { + continue; + } + resourceIDList.add(resourceID); + } + if (resourceIDList.isEmpty()) { + continue; + } + Map roleMap = new HashMap<>(); + roleMap.put("id", role); + roleMap.put("ids", resourceIDList); + roles.add(roleMap); + } + UserRequest request = new UserRequest(); + request.setId(userID); + request.setRoles(roles); + return request; + } + + public void addUserWorkspaceAndRole(UserRequest user, List userRoles) { + List> roles = user.getRoles(); + if (!roles.isEmpty()) { + insertUserRole(roles, user.getId()); + } + List list = userRoles.stream().map(UserRole::getSourceId).collect(Collectors.toList()); + if (!CollectionUtils.isEmpty(list)) { + if (list.contains(user.getLastWorkspaceId()) || list.contains(user.getLastOrganizationId())) { + user.setLastOrganizationId(""); + user.setLastWorkspaceId(""); + userMapper.updateByPrimaryKeySelective(user); + } + } + } } diff --git a/backend/src/main/java/io/metersphere/service/WorkspaceService.java b/backend/src/main/java/io/metersphere/service/WorkspaceService.java index 4589c99929..b2e073a71d 100644 --- a/backend/src/main/java/io/metersphere/service/WorkspaceService.java +++ b/backend/src/main/java/io/metersphere/service/WorkspaceService.java @@ -286,7 +286,11 @@ public class WorkspaceService { return projectMapper.selectByExample(projectExample); } - public List findAllIdAndName(){ - return extWorkspaceMapper.findAllIdAndName(); + public String getOrganizationIdById(String resourceID) { + return extWorkspaceMapper.getOrganizationIdById(resourceID); + } + + public List findIdAndNameByOrganizationId(String organizationId) { + return extWorkspaceMapper.findIdAndNameByOrganizationId(organizationId); } } diff --git a/backend/src/main/resources/i18n/messages.properties b/backend/src/main/resources/i18n/messages.properties index 108c7e15a7..cfdf27f777 100644 --- a/backend/src/main/resources/i18n/messages.properties +++ b/backend/src/main/resources/i18n/messages.properties @@ -40,6 +40,11 @@ user_import_phone_format_wrong= user_import_email_format_wrong= user_import_organization_not_fond= user_import_workspace_not_fond= +org_admin= +org_member= +test_manager= +tester= +read_only_user= module= preconditions_optional= step_tip_separate= diff --git a/backend/src/main/resources/i18n/messages_en_US.properties b/backend/src/main/resources/i18n/messages_en_US.properties index eed471852f..f3a48bdc0d 100644 --- a/backend/src/main/resources/i18n/messages_en_US.properties +++ b/backend/src/main/resources/i18n/messages_en_US.properties @@ -116,6 +116,11 @@ user_import_phone_format_wrong=Wrong phone format user_import_email_format_wrong=Wrong email format user_import_organization_not_fond=Organization is not found user_import_workspace_not_fond=Workspace is not found +org_admin=Organization manager +org_member=Organization member +test_manager=Test manager +tester=Tester +read_only_user=Read-only user module=Module preconditions_optional=Preconditions optional step_tip_separate=Each step is separated by a new line diff --git a/backend/src/main/resources/i18n/messages_zh_CN.properties b/backend/src/main/resources/i18n/messages_zh_CN.properties index 6935a01c0d..59b2025b15 100644 --- a/backend/src/main/resources/i18n/messages_zh_CN.properties +++ b/backend/src/main/resources/i18n/messages_zh_CN.properties @@ -116,6 +116,11 @@ user_import_phone_format_wrong=手机号码格式错误 user_import_email_format_wrong=电子邮箱格式错误 user_import_organization_not_fond=组织未找到 user_import_workspace_not_fond=工作空间未找到 +org_admin=组织管理员 +org_member=组织成员 +test_manager=测试经理 +tester=测试成员 +read_only_user=只读用户 module=模块 preconditions_optional=前置条件选填 step_tip_separate=每个步骤以换行分隔 diff --git a/backend/src/main/resources/i18n/messages_zh_TW.properties b/backend/src/main/resources/i18n/messages_zh_TW.properties index 3386da47a6..8f21283c9e 100644 --- a/backend/src/main/resources/i18n/messages_zh_TW.properties +++ b/backend/src/main/resources/i18n/messages_zh_TW.properties @@ -116,6 +116,11 @@ user_import_phone_format_wrong=手機號碼格式錯誤 user_import_email_format_wrong=電子郵箱格式錯誤 user_import_organization_not_fond=組織未找到 user_import_workspace_not_fond=工作空間未找到 +org_admin=組織管理員 +org_member=組織成員 +test_manager=測試經理 +tester=測試成員 +read_only_user=只讀用戶 module=模塊 preconditions_optional=前置條件選填 step_tip_separate=每個步驟以換行分隔 diff --git a/frontend/src/business/components/settings/organization/OrganizationMember.vue b/frontend/src/business/components/settings/organization/OrganizationMember.vue index cb5cbed546..a9e94e66a6 100644 --- a/frontend/src/business/components/settings/organization/OrganizationMember.vue +++ b/frontend/src/business/components/settings/organization/OrganizationMember.vue @@ -5,7 +5,22 @@ - + + + + + + + @@ -106,6 +121,7 @@ @confirm="updateOrgMember('updateUserForm')"/> + @@ -116,11 +132,22 @@ import MsRolesTag from "../../common/components/MsRolesTag"; import MsTableOperator from "../../common/components/MsTableOperator"; import MsDialogFooter from "../../common/components/MsDialogFooter"; - import {getCurrentUser, listenGoBack, removeGoBackListener} from "../../../../common/js/utils"; + import {getCurrentProjectID, getCurrentOrganizationId,getCurrentUser, listenGoBack, removeGoBackListener} from "../../../../common/js/utils"; + import MsTableHeaderSelectPopover from "@/business/components/common/components/table/MsTableHeaderSelectPopover"; + import { + _handleSelect, + _handleSelectAll, + getSelectDataCounts, + setUnSelectIds, + toggleAllSelection + } from "@/common/js/tableUtils"; + import UserCascader from "@/business/components/settings/system/components/UserCascader"; + import ShowMoreBtn from "@/business/components/track/case/components/ShowMoreBtn"; export default { name: "MsOrganizationMember", - components: {MsCreateBox, MsTablePagination, MsTableHeader, MsRolesTag, MsTableOperator, MsDialogFooter}, + components: {MsCreateBox, MsTablePagination, MsTableHeader, MsRolesTag, MsTableOperator, MsDialogFooter, + MsTableHeaderSelectPopover,UserCascader,ShowMoreBtn}, activated() { this.initTableData(); }, @@ -147,6 +174,21 @@ total: 0, options: [], loading: false, + selectDataCounts: 0, + batchAddLable: this.$t('project.please_choose_workspace'), + batchAddTitle: this.$t('project.batch_choose_workspace'), + selectRows: new Set(), + referenced: false, + batchAddWorkspaceOptions:[], + batchAddUserRoleOptions:[], + buttons: [ + { + name: this.$t('user.button.add_workspace_batch'), handleClick: this.addWorkspaceBatch + }, + { + name: this.$t('user.button.add_user_role_batch'), handleClick: this.addUserRoleBatch + } + ], } }, methods: { @@ -275,7 +317,78 @@ } else { this.options = []; } - } + }, + initWorkspaceBatchProcessDataStruct(isShow){ + let organizationId = getCurrentOrganizationId(); + this.$get("/user/getWorkspaceDataStruct/"+organizationId, response => { + this.batchAddWorkspaceOptions = response.data; + if(isShow){ + this.$refs.cascaderDialog.open('ADD_WORKSPACE',this.batchAddWorkspaceOptions); + } + }); + }, + initRoleBatchProcessDataStruct(isShow){ + let organizationId = getCurrentOrganizationId(); + this.$get("/user/getUserRoleDataStruct/"+organizationId, response => { + this.batchAddUserRoleOptions = response.data; + if(isShow){ + this.$refs.cascaderDialog.open('ADD_USER_ROLE',this.batchAddUserRoleOptions); + } + }); + }, + handleSelectAll(selection) { + _handleSelectAll(this, selection, this.tableData, this.selectRows); + setUnSelectIds(this.tableData, this.condition, this.selectRows); + this.selectDataCounts = getSelectDataCounts(this.condition, this.total, this.selectRows); + this.$emit('selection', selection); + }, + handleSelect(selection, row) { + _handleSelect(this, selection, row, this.selectRows); + setUnSelectIds(this.tableData, this.condition, this.selectRows); + this.selectDataCounts = getSelectDataCounts(this.condition, this.total, this.selectRows); + this.$emit('selection', selection); + }, + isSelectDataAll(data) { + this.condition.selectAll = data; + setUnSelectIds(this.tableData, this.condition, this.selectRows); + this.selectDataCounts = getSelectDataCounts(this.condition, this.total, this.selectRows); + toggleAllSelection(this.$refs.userTable, this.tableData, this.selectRows); + }, + addWorkspaceBatch(){ + if(this.batchAddWorkspaceOptions.length == 0){ + this.initWorkspaceBatchProcessDataStruct(true); + }else{ + this.$refs.cascaderDialog.open('ADD_WORKSPACE',this.batchAddWorkspaceOptions); + } + }, + addUserRoleBatch(){ + if(this.batchAddUserRoleOptions.length == 0){ + this.initRoleBatchProcessDataStruct(true); + }else{ + this.$refs.cascaderDialog.open('ADD_USER_ROLE',this.batchAddUserRoleOptions); + } + }, + cascaderConfirm(batchProcessTypeParam,selectValueArr){ + if(selectValueArr.length == 0){ + this.$success(this.$t('commons.modify_success')); + } + let params = {}; + params = this.buildBatchParam(params); + params.organizationId = getCurrentOrganizationId(); + params.batchType = batchProcessTypeParam; + params.batchProcessValue = selectValueArr; + this.$post('/user/special/batchProcessUserInfo', params, () => { + this.$success(this.$t('commons.modify_success')); + this.initTableData(); + this.$refs.cascaderDialog.close(); + }); + }, + buildBatchParam(param) { + param.ids = Array.from(this.selectRows).map(row => row.id); + param.projectId = getCurrentProjectID(); + param.condition = this.condition; + return param; + }, }, } @@ -296,4 +409,7 @@ width: 100%; } + /deep/ .ms-select-all-fixed th:nth-child(2) .el-icon-arrow-down { + top: -5px; + } diff --git a/frontend/src/business/components/settings/system/User.vue b/frontend/src/business/components/settings/system/User.vue index e7ce93f6a5..3f9da0158a 100644 --- a/frontend/src/business/components/settings/system/User.vue +++ b/frontend/src/business/components/settings/system/User.vue @@ -8,9 +8,25 @@ - + + + + + + + + @@ -335,12 +352,22 @@ import MsTableHeader from "../../common/components/MsTableHeader"; import MsTableOperator from "../../common/components/MsTableOperator"; import MsDialogFooter from "../../common/components/MsDialogFooter"; import MsTableOperatorButton from "../../common/components/MsTableOperatorButton"; -import {hasRole, listenGoBack, removeGoBackListener} from "@/common/js/utils"; +import {getCurrentProjectID, getUUID, hasRole, listenGoBack, removeGoBackListener} from "@/common/js/utils"; import MsRolesTag from "../../common/components/MsRolesTag"; import {ROLE_ADMIN} from "@/common/js/constants"; import {getCurrentUser} from "../../../../common/js/utils"; import {PHONE_REGEX} from "@/common/js/regex"; import UserImport from "@/business/components/settings/system/components/UserImport"; +import MsTableHeaderSelectPopover from "@/business/components/common/components/table/MsTableHeaderSelectPopover"; +import { + _handleSelect, + _handleSelectAll, + getSelectDataCounts, + setUnSelectIds, + toggleAllSelection +} from "@/common/js/tableUtils"; +import UserCascader from "@/business/components/settings/system/components/UserCascader"; +import ShowMoreBtn from "@/business/components/track/case/components/ShowMoreBtn"; export default { name: "MsUser", @@ -352,19 +379,28 @@ export default { MsDialogFooter, MsTableOperatorButton, MsRolesTag, - UserImport + UserImport, + MsTableHeaderSelectPopover, + UserCascader, + ShowMoreBtn }, data() { return { + referenced: false, queryPath: '/user/special/list', deletePath: '/user/special/delete/', createPath: '/user/special/add', updatePath: '/user/special/update', editPasswordPath: '/user/special/password', + batchAddLable: this.$t('project.please_choose_workspace'), + batchAddTitle: this.$t('project.batch_choose_workspace'), + batchAddWorkspaceOptions:[], + batchAddUserRoleOptions:[], result: {}, currentUserId: '', createVisible: false, updateVisible: false, + selectDataCounts: 0, editPasswordVisible: false, btnAddRole: false, multipleSelection: [], @@ -373,6 +409,7 @@ export default { pageSize: 10, total: 0, condition: {}, + selectRows: new Set(), tableData: [], form: { roles: [{ @@ -381,6 +418,14 @@ export default { }, checkPasswordForm: {}, ruleForm: {}, + buttons: [ + { + name: this.$t('user.button.add_workspace_batch'), handleClick: this.addWorkspaceBatch + }, + { + name: this.$t('user.button.add_user_role_batch'), handleClick: this.addUserRoleBatch + } + ], rule: { id: [ {required: true, message: this.$t('user.input_id'), trigger: 'blur'}, @@ -530,6 +575,8 @@ export default { if (!hasRole(ROLE_ADMIN)) { return; } + this.selectRows = new Set(); + this.condition.selectAll = false; this.result = this.$post(this.buildPagePath(this.queryPath), this.condition, response => { let data = response.data; this.total = data.itemCount; @@ -629,10 +676,89 @@ export default { } return value; }) - } + }, + initWorkspaceBatchProcessDataStruct(isShow){ + this.$get("/user/getWorkspaceDataStruct/All", response => { + this.batchAddWorkspaceOptions = response.data; + if(isShow){ + this.$refs.cascaderDialog.open('ADD_WORKSPACE',this.batchAddWorkspaceOptions); + } + }); + }, + initRoleBatchProcessDataStruct(isShow){ + this.$get("/user/getUserRoleDataStruct/All", response => { + this.batchAddUserRoleOptions = response.data; + if(isShow){ + this.$refs.cascaderDialog.open('ADD_USER_ROLE',this.batchAddUserRoleOptions); + } + }); + }, + handleSelectAll(selection) { + _handleSelectAll(this, selection, this.tableData, this.selectRows); + setUnSelectIds(this.tableData, this.condition, this.selectRows); + this.selectDataCounts = getSelectDataCounts(this.condition, this.total, this.selectRows); + this.$emit('selection', selection); + }, + handleSelect(selection, row) { + _handleSelect(this, selection, row, this.selectRows); + setUnSelectIds(this.tableData, this.condition, this.selectRows); + this.selectDataCounts = getSelectDataCounts(this.condition, this.total, this.selectRows); + this.$emit('selection', selection); + }, + isSelectDataAll(data) { + this.condition.selectAll = data; + setUnSelectIds(this.tableData, this.condition, this.selectRows); + this.selectDataCounts = getSelectDataCounts(this.condition, this.total, this.selectRows); + toggleAllSelection(this.$refs.userTable, this.tableData, this.selectRows); + }, + addWorkspaceBatch(){ + if(this.batchAddWorkspaceOptions.length == 0){ + this.initWorkspaceBatchProcessDataStruct(true); + }else{ + this.$refs.cascaderDialog.open('ADD_WORKSPACE',this.batchAddWorkspaceOptions); + } + }, + addUserRoleBatch(){ + if(this.batchAddUserRoleOptions.length == 0){ + this.initRoleBatchProcessDataStruct(true); + }else{ + this.$refs.cascaderDialog.open('ADD_USER_ROLE',this.batchAddUserRoleOptions); + } + }, + cascaderConfirm(batchProcessTypeParam,selectValueArr){ + if(selectValueArr.length == 0){ + this.$success(this.$t('commons.modify_success')); + } + let params = {}; + params = this.buildBatchParam(params); + params.batchType = batchProcessTypeParam; + params.batchProcessValue = selectValueArr; + this.$post('/user/special/batchProcessUserInfo', params, () => { + this.$success(this.$t('commons.modify_success')); + this.search(); + this.$refs.cascaderDialog.close(); + }); + }, + buildBatchParam(param) { + param.ids = Array.from(this.selectRows).map(row => row.id); + param.projectId = getCurrentProjectID(); + param.condition = this.condition; + return param; + }, } } diff --git a/frontend/src/business/components/settings/system/components/UserCascader.vue b/frontend/src/business/components/settings/system/components/UserCascader.vue new file mode 100644 index 0000000000..a6f1fd9790 --- /dev/null +++ b/frontend/src/business/components/settings/system/components/UserCascader.vue @@ -0,0 +1,124 @@ + + + + + + + diff --git a/frontend/src/business/components/settings/workspace/WorkspaceMember.vue b/frontend/src/business/components/settings/workspace/WorkspaceMember.vue index 7dccbe6ee6..e5e94eab81 100644 --- a/frontend/src/business/components/settings/workspace/WorkspaceMember.vue +++ b/frontend/src/business/components/settings/workspace/WorkspaceMember.vue @@ -5,7 +5,23 @@ - + + + + + + + + @@ -97,7 +113,7 @@ @confirm="updateWorkspaceMember('updateUserForm')"/> - + @@ -108,11 +124,27 @@ import MsRolesTag from "../../common/components/MsRolesTag"; import MsTableOperator from "../../common/components/MsTableOperator"; import MsDialogFooter from "../../common/components/MsDialogFooter"; - import {getCurrentUser, listenGoBack, removeGoBackListener} from "../../../../common/js/utils"; + import { + getCurrentOrganizationId, getCurrentProjectID, + getCurrentUser, + listenGoBack, + removeGoBackListener + } from "../../../../common/js/utils"; + import MsTableHeaderSelectPopover from "@/business/components/common/components/table/MsTableHeaderSelectPopover"; + import { + _handleSelect, + _handleSelectAll, + getSelectDataCounts, + setUnSelectIds, + toggleAllSelection + } from "@/common/js/tableUtils"; + import UserCascader from "@/business/components/settings/system/components/UserCascader"; + import ShowMoreBtn from "@/business/components/track/case/components/ShowMoreBtn"; export default { name: "MsMember", - components: {MsCreateBox, MsTablePagination, MsTableHeader, MsRolesTag, MsTableOperator, MsDialogFooter}, + components: {MsCreateBox, MsTablePagination, MsTableHeader, MsRolesTag, MsTableOperator, MsDialogFooter, + MsTableHeaderSelectPopover,UserCascader,ShowMoreBtn}, data() { return { result: {}, @@ -135,6 +167,17 @@ currentPage: 1, pageSize: 10, total: 0, + selectDataCounts: 0, + batchAddLable: this.$t('project.please_choose_workspace'), + batchAddTitle: this.$t('project.batch_choose_workspace'), + selectRows: new Set(), + referenced: false, + batchAddUserRoleOptions:[], + buttons: [ + { + name: this.$t('user.button.add_user_role_batch'), handleClick: this.addUserRoleBatch + } + ], } }, activated: function () { @@ -283,9 +326,62 @@ return (user.email.indexOf(queryString.toLowerCase()) === 0 || user.id.indexOf(queryString.toLowerCase()) === 0); }; }, - handleSelect(item) { - this.$set(this.form, "userId", item.id); - } + initRoleBatchProcessDataStruct(isShow){ + let organizationId = getCurrentOrganizationId(); + this.$get("/user/getWorkspaceUserRoleDataStruct/"+organizationId, response => { + this.batchAddUserRoleOptions = response.data; + if(isShow){ + this.$refs.cascaderDialog.open('ADD_USER_ROLE',this.batchAddUserRoleOptions); + } + }); + }, + handleSelectAll(selection) { + _handleSelectAll(this, selection, this.tableData, this.selectRows); + setUnSelectIds(this.tableData, this.condition, this.selectRows); + this.selectDataCounts = getSelectDataCounts(this.condition, this.total, this.selectRows); + this.$emit('selection', selection); + }, + handleSelect(selection, row) { + _handleSelect(this, selection, row, this.selectRows); + setUnSelectIds(this.tableData, this.condition, this.selectRows); + this.selectDataCounts = getSelectDataCounts(this.condition, this.total, this.selectRows); + this.$emit('selection', selection); + this.$set(this.form, "userId", selection.id); + }, + isSelectDataAll(data) { + this.condition.selectAll = data; + setUnSelectIds(this.tableData, this.condition, this.selectRows); + this.selectDataCounts = getSelectDataCounts(this.condition, this.total, this.selectRows); + toggleAllSelection(this.$refs.userTable, this.tableData, this.selectRows); + }, + addUserRoleBatch(){ + if(this.batchAddUserRoleOptions.length == 0){ + this.initRoleBatchProcessDataStruct(true); + }else{ + this.$refs.cascaderDialog.open('ADD_USER_ROLE',this.batchAddUserRoleOptions); + } + }, + cascaderConfirm(batchProcessTypeParam,selectValueArr){ + if(selectValueArr.length == 0){ + this.$success(this.$t('commons.modify_success')); + } + let params = {}; + params = this.buildBatchParam(params); + params.organizationId = getCurrentOrganizationId(); + params.batchType = batchProcessTypeParam; + params.batchProcessValue = selectValueArr; + this.$post('/user/special/batchProcessUserInfo', params, () => { + this.$success(this.$t('commons.modify_success')); + this.initTableData(); + this.$refs.cascaderDialog.close(); + }); + }, + buildBatchParam(param) { + param.ids = Array.from(this.selectRows).map(row => row.id); + param.projectId = getCurrentProjectID(); + param.condition = this.condition; + return param; + }, } } @@ -314,4 +410,7 @@ width: 100%; } + /deep/ .ms-select-all-fixed th:nth-child(2) .el-icon-arrow-down { + top: -5px; + } diff --git a/frontend/src/i18n/en-US.js b/frontend/src/i18n/en-US.js index 57fc60bf4c..ba60fb987f 100644 --- a/frontend/src/i18n/en-US.js +++ b/frontend/src/i18n/en-US.js @@ -237,6 +237,7 @@ export default { search_by_name: 'Search by name', organization_name: 'Organization Name', please_choose_organization: 'Please Choose Organization', + batch_choose_workspace: 'Please choose organizations', please_select_a_workspace_first: 'Please select a workspace first!', none: 'None Workspace', select: 'Select Workspace', @@ -372,7 +373,11 @@ export default { delete_confirm: 'Are you sure you want to delete this User?', apikey_delete_confirm: 'Are you sure you want to delete this API Key?', input_id_placeholder: 'Please enter ID (Chinese characters are not supported)', - source: 'Source' + source: 'Source', + button:{ + add_workspace_batch: 'Batch add user to workspace', + add_user_role_batch: 'Batch add user role', + } }, role: { please_choose_role: 'Please Choose Role', diff --git a/frontend/src/i18n/zh-CN.js b/frontend/src/i18n/zh-CN.js index 0474448a2e..248e541aa8 100644 --- a/frontend/src/i18n/zh-CN.js +++ b/frontend/src/i18n/zh-CN.js @@ -326,6 +326,7 @@ export default { input_name: '请输入项目名称', owning_workspace: '所属工作空间', please_choose_workspace: '请选择工作空间', + batch_choose_workspace: '批量选择工作空间', special_characters_are_not_supported: '格式错误(不支持特殊字符,且不能以\'-\'开头结尾)', tapd_id: 'TAPD项目ID', jira_key: 'JIRA项目key', @@ -373,7 +374,11 @@ export default { delete_confirm: '这个用户确定要删除吗?', apikey_delete_confirm: '这个 API Key 确定要删除吗?', input_id_placeholder: '请输入ID (不支持中文)', - source: '用户来源' + source: '用户来源', + button:{ + add_workspace_batch: '批量添加到工作空间', + add_user_role_batch: '批量添加角色', + } }, role: { please_choose_role: '请选择角色', diff --git a/frontend/src/i18n/zh-TW.js b/frontend/src/i18n/zh-TW.js index 638240799e..3830658161 100644 --- a/frontend/src/i18n/zh-TW.js +++ b/frontend/src/i18n/zh-TW.js @@ -323,6 +323,7 @@ export default { input_name: '請輸入項目名稱', owning_workspace: '所屬工作空間', please_choose_workspace: '請選擇工作空間', + batch_choose_workspace: '批量選擇工作空間', special_characters_are_not_supported: '格式錯誤(不支持特殊字符,且不能以\'-\'開頭結尾)', tapd_id: 'TAPD項目ID', jira_key: 'JIRA項目key', @@ -370,7 +371,11 @@ export default { delete_confirm: '這個用戶確定要刪除嗎?', apikey_delete_confirm: '這個 API Key 確定要刪除嗎?', input_id_placeholder: '請輸入ID (不支持中文)', - source: '用戶來源' + source: '用戶來源', + button:{ + add_workspace_batch: '批量添加到工作空間', + add_user_role_batch: '批量添加角色', + } }, role: { please_choose_role: '請選擇角色',