From 1a3aabdb7bce68b60cf8dd5787935aeede5d656d Mon Sep 17 00:00:00 2001 From: Ravi Lodhi Date: Mon, 9 Sep 2024 15:29:47 +0530 Subject: [PATCH] Improved: Added support to link user to multiple security groups (#267). --- .../SecurityGroupActionsPopover.vue | 107 +++++++++++ src/components/SelectSecurityGroupModal.vue | 124 +++++++++++++ src/services/UserService.ts | 23 ++- src/store/modules/user/actions.ts | 2 +- src/store/modules/user/getters.ts | 3 + src/views/UserDetails.vue | 166 +++++++++++------- 6 files changed, 347 insertions(+), 78 deletions(-) create mode 100644 src/components/SecurityGroupActionsPopover.vue create mode 100644 src/components/SelectSecurityGroupModal.vue diff --git a/src/components/SecurityGroupActionsPopover.vue b/src/components/SecurityGroupActionsPopover.vue new file mode 100644 index 0000000..8fbee0f --- /dev/null +++ b/src/components/SecurityGroupActionsPopover.vue @@ -0,0 +1,107 @@ + + + \ No newline at end of file diff --git a/src/components/SelectSecurityGroupModal.vue b/src/components/SelectSecurityGroupModal.vue new file mode 100644 index 0000000..2027901 --- /dev/null +++ b/src/components/SelectSecurityGroupModal.vue @@ -0,0 +1,124 @@ + + + + \ No newline at end of file diff --git a/src/services/UserService.ts b/src/services/UserService.ts index c9a75d9..73ac6b7 100644 --- a/src/services/UserService.ts +++ b/src/services/UserService.ts @@ -292,15 +292,15 @@ const sendResetPasswordEmail = async (payload: any): Promise => { } -const getUserSecurityGroup = async (userLoginId: string): Promise => { - let userSecurityGroup = {} as any +const getUserSecurityGroups = async (userLoginId: string): Promise => { + let userSecurityGroups = [] as any const payload = { inputFields: { userLoginId, }, entityName: "UserLoginSecurityGroup", filterByDate: "Y", - viewSize: 10, + viewSize: 20, //Don't think there will be more than 20 security groups associated with any user fieldList: ["groupId", "userLoginId", "fromDate"] } @@ -313,18 +313,15 @@ const getUserSecurityGroup = async (userLoginId: string): Promise => { if (!hasError(resp) || resp.data.error === 'No record found') { - userSecurityGroup = { - groupId: resp.data.docs ? resp.data.docs[0].groupId : '', - fromDate: resp.data.docs && resp.data.docs[0].fromDate - } + userSecurityGroups = resp.data.docs ? resp.data.docs : [] } else { throw resp.data } } catch (error) { - logger.error('Failed to fetch user associated security group.', error) + logger.error('Failed to fetch user associated security groups.', error) } - return userSecurityGroup + return userSecurityGroups } const removeUserSecurityGroup = async (payload: any): Promise => { @@ -337,7 +334,7 @@ const removeUserSecurityGroup = async (payload: any): Promise => { const addUserToSecurityGroup = async (payload: any): Promise => { return api({ - url: "service/addSecurityGroupToUserLogin", + url: "service/addPartyUserPermission", method: "post", data: payload }); @@ -536,8 +533,8 @@ const finishSetup = async (payload: any): Promise => { }); if (!hasError(resp)) { addUserToSecurityGroup({ - "partyIdTo": partyId, - "securityGroupId": payload.selectedTemplate.securityGroupId ? payload.selectedTemplate.securityGroupId : "STORE_MANAGER", + "userLoginId": payload.formData.userLoginId, + "groupIds": payload.selectedTemplate.securityGroupId ? [payload.selectedTemplate.securityGroupId] : ["STORE_MANAGER"], }); } else { throw resp.data; @@ -786,7 +783,7 @@ export const UserService = { getUserProfile, getUserFacilities, getUserProductStores, - getUserSecurityGroup, + getUserSecurityGroups, isUserLoginIdAlreadyExists, isRoleTypeExists, login, diff --git a/src/store/modules/user/actions.ts b/src/store/modules/user/actions.ts index a2c3b70..f966ed8 100644 --- a/src/store/modules/user/actions.ts +++ b/src/store/modules/user/actions.ts @@ -232,7 +232,7 @@ const actions: ActionTree = { if (Object.keys(selectedUser).length) { selectedUser.facilities = await UserService.getUserFacilities(selectedUser.partyId) - selectedUser.securityGroup = await UserService.getUserSecurityGroup(selectedUser.userLoginId) + selectedUser.securityGroups = await UserService.getUserSecurityGroups(selectedUser.userLoginId) selectedUser.productStores = await UserService.getUserProductStores(selectedUser.partyId) if (selectedUser.userLoginId) { const userFavorites = await UserService.getUserFavorites({userLoginId: selectedUser.userLoginId}) diff --git a/src/store/modules/user/getters.ts b/src/store/modules/user/getters.ts index 360466c..0dea144 100644 --- a/src/store/modules/user/getters.ts +++ b/src/store/modules/user/getters.ts @@ -44,5 +44,8 @@ const getters: GetterTree = { getUserProductStores(state) { return state.selectedUser.productStores; }, + getUserSecurityGroups(state) { + return state.selectedUser.securityGroups; + } } export default getters; \ No newline at end of file diff --git a/src/views/UserDetails.vue b/src/views/UserDetails.vue index 26f82b6..3d9fb23 100644 --- a/src/views/UserDetails.vue +++ b/src/views/UserDetails.vue @@ -258,7 +258,40 @@ {{ translate('Clearance') }} - + + + + {{ translate('Add to security group') }} + + + + {{ translate('Security Group') }} + + {{ translate('Add') }} + + + + + + + + + {{ translate('Add to a product store') }} @@ -292,6 +326,7 @@ + @@ -322,7 +357,7 @@ {{ translate("Show as picker") }} - + {{ getUserFacilities().length === 1 ? translate('Added to 1 facility') : translate('Added to facilities', { count: getUserFacilities().length }) }} @@ -442,9 +477,11 @@ import { import { translate } from '@hotwax/dxp-components'; import ContactActionsPopover from '@/components/ContactActionsPopover.vue' import ProductStoreActionsPopover from '@/components/ProductStoreActionsPopover.vue' +import SecurityGroupActionsPopover from '@/components/SecurityGroupActionsPopover.vue' import ResetPasswordModal from '@/components/ResetPasswordModal.vue' import SelectFacilityModal from '@/components/SelectFacilityModal.vue' import SelectProductStoreModal from '@/components/SelectProductStoreModal.vue' +import SelectSecurityGroupModal from '@/components/SelectSecurityGroupModal.vue' import { UserService } from "@/services/UserService"; import { isValidEmail, isValidPassword, showToast } from "@/utils"; import { hasError } from '@/adapter'; @@ -488,6 +525,7 @@ export default defineComponent({ ...mapGetters({ selectedUser: 'user/getSelectedUser', userProductStores: 'user/getUserProductStores', + userSecurityGroups: 'user/getUserSecurityGroups', getRoleTypeDesc: 'util/getRoleTypeDesc', securityGroups: 'util/getSecurityGroups', userProfile: 'user/getUserProfile', @@ -538,6 +576,13 @@ export default defineComponent({ this.username = this.selectedUser.groupName ? (this.selectedUser.groupName)?.toLowerCase() : (`${this.selectedUser.firstName}.${this.selectedUser.lastName}`?.toLowerCase()) }, methods: { + checkUserAssociatedSecurityGroup(securityGroupId: any) { + return this.userSecurityGroups?.some((userSecurityGroup:any) => userSecurityGroup.groupId === securityGroupId) + }, + getSecurityGroupName(securityGroupId: any) { + const group = this.securityGroups.find((group: any) => group.groupId === securityGroupId); + return group?.groupName || group?.groupId || null; + }, getShopifyShops(productStoreId: string) { this.shopifyShopsForProductStore = this.shopifyShops.filter((shopifyShop:any) => shopifyShop.productStoreId === productStoreId); }, @@ -789,6 +834,17 @@ export default defineComponent({ await alert.present(); }, + async openSecurityGroupActionsPopover(event: Event, securityGroup: any) { + const securityGroupActionsPopover = await popoverController.create({ + component: SecurityGroupActionsPopover, + componentProps: { + securityGroup : {...securityGroup, groupName: this.getSecurityGroupName(securityGroup.groupId)} + }, + event, + showBackdrop: false, + }); + return securityGroupActionsPopover.present(); + }, async openProductStoreActionsPopover(event: Event, store: any) { const productStoreActionsPopover = await popoverController.create({ component: ProductStoreActionsPopover, @@ -868,6 +924,43 @@ export default defineComponent({ }) return selectFacilityModal.present(); }, + async selectSecurityGroup() { + const selectSecurityGroupModal = await modalController.create({ + component: SelectSecurityGroupModal, + componentProps: { selectedSecurityGroups: this.userSecurityGroups } + }); + + selectSecurityGroupModal.onDidDismiss().then(async (result) => { + if (result.data && result.data.value) { + const securityGroupsToCreate = result.data.value.securityGroupsToCreate + const securityGroupsToRemove = result.data.value.securityGroupsToRemove + + const updateResponses = await Promise.allSettled(securityGroupsToRemove + .map(async (payload: any) => await UserService.removeUserSecurityGroup({ + groupId: payload.groupId, + userLoginId: this.selectedUser.userLoginId + })) + ) + + const createResponse = await UserService.addUserToSecurityGroup({ + groupIds: securityGroupsToCreate?.map((group: any) => group.groupId), + userLoginId: this.selectedUser.userLoginId + }) + + const hasFailedResponse = [...updateResponses, createResponse].some((response: any) => response.status === 'rejected') + if (hasFailedResponse) { + showToast(translate('Failed to update some security group(s).')) + } else { + showToast(translate('Security group(s) updated successfully.')) + } + // refetching security groups + const userSecurityGroups = await UserService.getUserSecurityGroups(this.selectedUser.userLoginId) + this.store.dispatch('user/updateSelectedUser', { ...this.selectedUser, securityGroups: userSecurityGroups }) + } + }) + + return selectSecurityGroupModal.present(); + }, async selectProductStore() { const selectProductStoreModal = await modalController.create({ component: SelectProductStoreModal, @@ -929,63 +1022,6 @@ export default defineComponent({ return selectProductStoreModal.present(); }, - async updateSecurityGroup(event: CustomEvent) { - const groupId = event.detail.value - // stop programmatic update as ion-change is triggered on page mount automatically - if (groupId === this.selectedUser.securityGroup.groupId) { - return - } - - let resp = {} as any - try { - // delete if none (empty groupId) selected - if (!groupId) { - resp = await UserService.removeUserSecurityGroup({ - groupId: this.selectedUser.securityGroup.groupId, - userLoginId: this.selectedUser.userLoginId - }) - if (!hasError(resp)) { - showToast(translate('Security group updated successfully.')) - const userSecurityGroup = await UserService.getUserSecurityGroup(this.selectedUser.userLoginId) - this.store.dispatch('user/updateSelectedUser', { ...this.selectedUser, securityGroup: userSecurityGroup }) - } else { - throw resp.data - } - } else if (this.selectedUser.securityGroup.groupId) { - //update if already associated, this will expire existing user security groups and associate the new one. - resp = await UserService.addUserToSecurityGroup({ - securityGroupId: groupId, - partyIdTo: this.selectedUser.partyId - }) - if (hasError(resp)) throw resp.data - showToast(translate('Security group updated successfully.')) - const userSecurityGroup = await UserService.getUserSecurityGroup(this.selectedUser.userLoginId) - this.store.dispatch('user/updateSelectedUser', { ...this.selectedUser, securityGroup: userSecurityGroup }) - - } else { - // create if not associated - resp = await UserService.addUserToSecurityGroup({ - securityGroupId: groupId, - partyIdTo: this.selectedUser.partyId - }) - if (!hasError(resp)) { - showToast(translate('Security group updated successfully.')) - const userSecurityGroup = await UserService.getUserSecurityGroup(this.selectedUser.userLoginId) - this.store.dispatch('user/updateSelectedUser', { ...this.selectedUser, securityGroup: userSecurityGroup }) - } else { - throw resp.data - } - } - } catch (error: any) { - const errorMessage = error?.response?.data?.error.message; - if (errorMessage && errorMessage?.startsWith('Security Error:')) { - showToast(translate(translate("You don't have permission to update the security group."))) - } else { - showToast(translate('Something went wrong.')) - } - logger.error(error) - } - }, async updatePickerRoleStatus(event: any) { event.stopImmediatePropagation(); const isChecked = !event.target.checked; @@ -1178,15 +1214,17 @@ export default defineComponent({ getSecurityGroups(securityGroups: any) { const excludedSecurityGroups = JSON.parse(process.env.VUE_APP_EXCLUDED_SECURITY_GROUPS as string) - const selectedSecurityGroup = this.selectedUser.securityGroup.groupId + const selectedSecurityGroupIds = this.selectedUser.securityGroups.map((securityGroup: any) => securityGroup.groupId) if(!hasPermission(Actions.APP_SUPER_USER)) excludedSecurityGroups.push('SUPER') // We have some excluded security groups that can't be created by any users, // But if a user exists of these excluded security groups, we will show them in the select option. - if(excludedSecurityGroups.includes(selectedSecurityGroup)) { - excludedSecurityGroups.splice(excludedSecurityGroups.indexOf(selectedSecurityGroup), 1) - } + selectedSecurityGroupIds.map((selectedSecurityGroupId:any) => { + if(excludedSecurityGroups.includes(selectedSecurityGroupId)) { + excludedSecurityGroups.splice(excludedSecurityGroups.indexOf(selectedSecurityGroupId), 1) + } + }) return securityGroups.filter((group: any) => !excludedSecurityGroups.includes(group.groupId)) },