From 2b95cff4b5ac32204891670e1978c385670bfe55 Mon Sep 17 00:00:00 2001 From: Pavindu Lakshan Date: Fri, 11 Oct 2024 07:52:14 +0530 Subject: [PATCH] Bug fixes related to conosle settings in primary organizations for managed deployments --- .../add-admin-user-basic.tsx | 513 ++++++++++ .../add-external-admin-wizard.tsx | 894 ++++++++++++++++++ .../administrators-list.tsx | 2 +- .../console-administrators.tsx | 4 +- .../console-roles-edit.scss | 5 + .../console-roles-edit/console-roles-edit.tsx | 88 +- .../components/edit-role/edit-role-users.tsx | 74 +- features/admin.roles.v2/models/roles.ts | 7 + 8 files changed, 1523 insertions(+), 64 deletions(-) create mode 100644 features/admin.console-settings.v1/components/console-administrators/add-external-admin-wizard/add-admin-user-basic.tsx create mode 100644 features/admin.console-settings.v1/components/console-administrators/add-external-admin-wizard/add-external-admin-wizard.tsx diff --git a/features/admin.console-settings.v1/components/console-administrators/add-external-admin-wizard/add-admin-user-basic.tsx b/features/admin.console-settings.v1/components/console-administrators/add-external-admin-wizard/add-admin-user-basic.tsx new file mode 100644 index 00000000000..e09dad53f60 --- /dev/null +++ b/features/admin.console-settings.v1/components/console-administrators/add-external-admin-wizard/add-admin-user-basic.tsx @@ -0,0 +1,513 @@ +/** + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { FeatureAccessConfigInterface } from "@wso2is/access-control"; +import { InternalAdminFormDataInterface } from "@wso2is/admin.administrators.v1/models"; +import { isAdminUser, isCollaboratorUser } from "@wso2is/admin.administrators.v1/utils/administrators"; +import { useApplicationList } from "@wso2is/admin.applications.v1/api/application"; +import { ApplicationManagementConstants } from "@wso2is/admin.applications.v1/constants"; +import { + AppState, + SharedUserStoreUtils, + UIConstants, + UserBasicInterface, + getUsersList +} from "@wso2is/admin.core.v1"; +import { EventPublisher } from "@wso2is/admin.core.v1/utils"; +import { administratorConfig } from "@wso2is/admin.extensions.v1/configs/administrator"; +import { SCIMConfigs } from "@wso2is/admin.extensions.v1/configs/scim"; +import { getRolesList } from "@wso2is/admin.roles.v2/api"; +import { AdminAccountTypes } from "@wso2is/admin.users.v1/constants/user-management-constants"; +import { UserInviteInterface, UserListInterface } from "@wso2is/admin.users.v1/models/user"; +import { UserManagementUtils } from "@wso2is/admin.users.v1/utils"; +import { getUserNameWithoutDomain } from "@wso2is/core/helpers"; +import { IdentifiableComponentInterface, RolesInterface } from "@wso2is/core/models"; +import { Field, FormValue, Forms, Validation } from "@wso2is/forms"; +import { + ContentLoader, + DocumentationLink, + Heading, + Hint, + TransferComponent, + TransferList, + TransferListItem, + useDocumentation +} from "@wso2is/react-components"; +import { FormValidation } from "@wso2is/validation"; +import { AxiosResponse } from "axios"; +import debounce, { DebouncedFunc } from "lodash-es/debounce"; +import isEmpty from "lodash-es/isEmpty"; +import kebabCase from "lodash-es/kebabCase"; +import React, { FormEvent, ReactElement, useCallback, useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useSelector } from "react-redux"; +import { Divider, Dropdown, DropdownDivider, DropdownItem, DropdownMenu, DropdownProps, Grid, Header } from "semantic-ui-react"; + +/** + * Proptypes for the add admin user basic component. + */ +interface AddAdminUserBasicProps extends IdentifiableComponentInterface { + triggerSubmit: boolean; + onSubmit: (values: any) => void; + administratorType: string; + setFinishButtonDisabled: (values: boolean) => void; +} + +/** + * Add admin user basic component. + * + * @returns add admin modal component. + */ +export const AddAdminUserBasic: React.FunctionComponent = ( + props: AddAdminUserBasicProps): ReactElement => { + + const { + administratorType, + triggerSubmit, + onSubmit, + setFinishButtonDisabled, + [ "data-componentid"]: componentId + } = props; + + const [ userRoleOptions, setUserRoleList ] = useState([]); + const [ rolesList, setRolesList ] = useState([]); + const [ usersList, setUsersList ] = useState([]); + const [ checkedAssignedListItems, setCheckedAssignedListItems ] = useState([]); + const [ isUserListRequestLoading, setUserListRequestLoading ] = useState(false); + const [ isUserRoleOptionsRequestLoading, setUserRoleOptionsRequestLoading ] = useState(false); + const [ listItemLimit, setListItemLimit ] = useState(0); + const [ userListMetaContent, setUserListMetaContent ] = useState(undefined); + const [ searchQuery, setSearchQuery ] = useState(""); + const [ listOffset ] = useState(0); + + const { t } = useTranslation(); + const { getLink } = useDocumentation(); + + const consoleSettingsFeatureConfig: FeatureAccessConfigInterface = useSelector((state: AppState) => state?.config?.ui?.features?.consoleSettings); + const isInvitedAdminInConsoleSettingsEnabled: boolean = !consoleSettingsFeatureConfig?.disabledFeatures?.includes( + "consoleSettings.invitedExternalAdmins" + ); + + /** + * Retrieve the application data for the console application, filtering by name. + */ + const { data: applicationListData } = useApplicationList( + null, + null, + null, + `name eq ${ApplicationManagementConstants.CONSOLE_APP_NAME}` + ); + + /** + * Build the roles filter to search for roles specific to the console application. + */ + const roleSearchFilter: string = useMemo(() => { + if (applicationListData?.applications && applicationListData?.applications?.length > 0) { + return `audience.value eq ${applicationListData?.applications[0]?.id}`; + } + + return null; + }, [ applicationListData ]); + + // Username input validation error messages. + const USERNAME_REGEX_VIOLATION_ERROR_MESSAGE: string = t("users:guestUsers.fields." + + "username.validations.regExViolation"); + + const eventPublisher: EventPublisher = EventPublisher.getInstance(); + + useEffect(() => { + if (!roleSearchFilter) { + return; + } + + // Fetch users to select as internal admins. + if (administratorType === AdminAccountTypes.INTERNAL) { + setListItemLimit(UIConstants.DEFAULT_RESOURCE_LIST_ITEM_LIMIT); + setUserListMetaContent(new Map([ + [ "name", "name" ], + [ "emails", "emails" ], + [ "roles", "roles" ], + [ "userName", "userName" ], + [ "id", "" ], + [ "profileUrl", "profileUrl" ], + [ "meta.lastModified", "meta.lastModified" ], + [ "meta.created", "" ] + ])); + } + + const roleOptions: DropdownProps[] = []; + + if (userRoleOptions.length === 0) { + setUserRoleOptionsRequestLoading(true); + getRolesList(null, roleSearchFilter) + .then((response: AxiosResponse) => { + setRolesList(response.data.Resources); + response.data.Resources.map((role: RolesInterface, index: number) => { + if(role.displayName === "system" || + role.displayName === "everyone" || + role.displayName === "selfsignup" + ) { + return; + } + + if ( + role.displayName?.split("/")?.length < 2 && + role.displayName?.split("/")[0] === "Application" + ) { + return; + } + + if (isInvitedAdminInConsoleSettingsEnabled || + ( + !isInvitedAdminInConsoleSettingsEnabled && role.meta?.systemRole + ) + + ){ + roleOptions?.push({ + key: index, + text: role?.displayName, + value: role?.displayName + }); + } + }); + setUserRoleList(roleOptions); + }).finally(() => { + setUserRoleOptionsRequestLoading(false); + }); + } + }, [ roleSearchFilter ]); + + useEffect(() => { + if (userListMetaContent) { + const attributes: string = generateAttributesString(userListMetaContent.values()); + + if (isEmpty(searchQuery)) { + setUsersList([]); + + return; + } + + getUserList(listItemLimit, listOffset, searchQuery, attributes, null); + } + }, [ listOffset, listItemLimit, searchQuery ]); + + useEffect(() => { + if (administratorType === AdminAccountTypes.INTERNAL) { + setFinishButtonDisabled(isEmpty(checkedAssignedListItems)); + } + }, [ checkedAssignedListItems ]); + + /** + * The following method accepts a Map and returns the values as a string. + * + * @param attributeMap - IterableIterator + * @returns attribute string + */ + const generateAttributesString = (attributeMap: IterableIterator) => { + const attArray: string[] = []; + const iterator1: IterableIterator = attributeMap[ Symbol.iterator ](); + + for (const attribute of iterator1) { + if (attribute !== "") { + attArray.push(attribute); + } + } + + return attArray.toString(); + }; + + const getUserList = (limit: number, offset: number, filter: string, attribute: string, userStore: string) => { + + setUserListRequestLoading(true); + + getUsersList(limit, offset, filter, attribute, userStore) + .then((response: UserListInterface) => { + // Exclude JIT users, internal admin users and collaborators. + const responseUsers: UserBasicInterface[] = response?.Resources?.filter( + (user: UserBasicInterface) => + !user[ SCIMConfigs.scim.enterpriseSchema ]?.userSourceId && + !isAdminUser(user) && + !isCollaboratorUser(user)); + + if (responseUsers) { + responseUsers.sort((userObject: UserBasicInterface, comparedUserObject: UserBasicInterface) => + userObject.name?.givenName?.localeCompare(comparedUserObject.name?.givenName) + ); + + setUsersList(responseUsers); + } else { + setUsersList([]); + } + }) + .finally(() => { + setUserListRequestLoading(false); + }); + }; + + const getFormValues = (values: Map): void => { + eventPublisher.publish("manage-users-collaborator-role", { + type: kebabCase(values.get("role").toString()) + }); + + const inviteUser: UserInviteInterface = { + email: values.get("email").toString(), + roles: [ values.get("role").toString() ] + }; + + if (triggerSubmit) { + onSubmit(inviteUser); + } + }; + + /** + * This is a debounced function to handle the user search by email address. + * Debounced to limit getUsers() api call. + * @param FormEvent - form event + * @param string - query + */ + const handleSearchFieldChange: DebouncedFunc<(e: FormEvent, query: string) => void> + = useCallback(debounce((e: FormEvent, query: string) => { + if (query === "") { + setSearchQuery(""); + } else { + const processedQuery: string = "emails co " + query; + + setSearchQuery(processedQuery); + } + }, 1000), []); + + const handleAssignedItemCheckboxChange = (user: UserBasicInterface) => { + const checkedUsers: UserBasicInterface[] = [ ...checkedAssignedListItems ]; + + if (checkedUsers.some((item: UserBasicInterface) => item.id === user.id)) { + checkedUsers.splice(checkedUsers.indexOf(user), 1); + setCheckedAssignedListItems(checkedUsers); + } else { + checkedUsers.push(user); + setCheckedAssignedListItems(checkedUsers); + } + }; + + const resolveListItemElement = (listItemValue: string) => { + + return ( + <> + { listItemValue } + + ); + }; + + const processInternalAdminFormData = (roleName: string): void => { + const selectedRoles: RolesInterface[] = rolesList?.filter( + (role: RolesInterface) => role.displayName == roleName); + const processedFormData: InternalAdminFormDataInterface = { + checkedUsers: checkedAssignedListItems, + selectedRoles: selectedRoles + }; + + if (triggerSubmit) { + onSubmit(processedFormData); + } + }; + + /** + * The modal to add new user internally. + */ + const inviteInternalUserForm = () => ( + processInternalAdminFormData(administratorConfig.adminRoleName) } + submitState={ triggerSubmit } + > + + { t("extensions:manage.users.wizard.addAdmin.internal.selectUser") } + + + { t("extensions:manage.users.wizard.addAdmin.internal.hint") } + + , + { value }: { value: string; }) => { + handleSearchFieldChange(e, value); + } } + data-componentid={ `${ componentId }-transfer-component` } + > + + { + usersList?.map((user: UserBasicInterface, index: number) => { + + const header: string = getUserNameWithoutDomain(user?.userName); + const subHeader: string = UserManagementUtils.resolveUserListSubheader(user); + + return ( + handleAssignedItemCheckboxChange(user) } + key={ index } + listItem={ { + listItemElement: resolveListItemElement(header), + listItemValue: subHeader + } } + listItemId={ user.id } + listItemIndex={ index } + isItemChecked={ checkedAssignedListItems.some((item: UserBasicInterface) => + item.id === user.id) } + showSecondaryActions={ false } + showListSubItem={ true } + listSubItem={ header !== subHeader && ( +
+ + + { subHeader } + + +
+ ) } + data-componentid={ `${ componentId }-unselected-transfer-list-item-${ index }` } + /> + ); + } ) + } +
+
+
+ ); + + /** + * The modal to add new user externally. + */ + const inviteExternalUserForm = () => ( + ) => { + onSubmit(getFormValues(values)); + } } + submitState={ triggerSubmit } + > + { + (isUserRoleOptionsRequestLoading || + !userRoleOptions || + userRoleOptions?.length <= 0 + ) ? ( + + ) : ( + + + + { + // Check whether username is a valid email. + // check username validity against userstore regex + if (value && (!FormValidation.email(value) || !SharedUserStoreUtils + .validateInputAgainstRegEx(value, window["AppUtils"] + .getConfig().extensions.collaboratorUsernameRegex))) { + validation.isValid = false; + validation.errorMessages.push(USERNAME_REGEX_VIOLATION_ERROR_MESSAGE); + } + } } + type="email" + tabIndex={ 5 } + maxLength={ 50 } + /> + + + + + { + + } + + + { "Select a role to assign to the user." + + " The access level of the user is determined by the role." } + + { t("extensions:common.learnMore") } + + + + + + ) + } + + ); + + return ( + administratorType === AdminAccountTypes.INTERNAL + ? inviteInternalUserForm() + : inviteExternalUserForm() + ); +}; + +/** + * Default props for the component. + */ +AddAdminUserBasic.defaultProps = { + "data-componentid": "add-admin-user-basic" +}; diff --git a/features/admin.console-settings.v1/components/console-administrators/add-external-admin-wizard/add-external-admin-wizard.tsx b/features/admin.console-settings.v1/components/console-administrators/add-external-admin-wizard/add-external-admin-wizard.tsx new file mode 100644 index 00000000000..ac968f0e0cd --- /dev/null +++ b/features/admin.console-settings.v1/components/console-administrators/add-external-admin-wizard/add-external-admin-wizard.tsx @@ -0,0 +1,894 @@ +/** + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { InternalAdminFormDataInterface } from "@wso2is/admin.administrators.v1/models"; +import { isAdminUser } from "@wso2is/admin.administrators.v1/utils/administrators"; +import { + AddAdminUserBasic +} from "@wso2is/admin.administrators.v1/wizard/steps/admin-user-basic"; +import { useApplicationList } from "@wso2is/admin.applications.v1/api"; +import { ApplicationManagementConstants } from "@wso2is/admin.applications.v1/constants"; +import { UserBasicInterface } from "@wso2is/admin.core.v1"; +import { administratorConfig } from "@wso2is/admin.extensions.v1/configs/administrator"; +import { updateRoleDetails, useRolesList } from "@wso2is/admin.roles.v2/api/roles"; +import { PatchRoleDataInterface } from "@wso2is/admin.roles.v2/models/roles"; +import { sendInvite, useUsersList } from "@wso2is/admin.users.v1/api"; +import { getUserWizardStepIcons } from "@wso2is/admin.users.v1/configs/ui"; +import { AdminAccountTypes, UserManagementConstants } from "@wso2is/admin.users.v1/constants/user-management-constants"; +import { UserInviteInterface } from "@wso2is/admin.users.v1/models"; +import { PRIMARY_USERSTORE } from "@wso2is/admin.userstores.v1/constants/user-store-constants"; +import { + AlertLevels, + IdentifiableComponentInterface, + RolesInterface, + TestableComponentInterface +} from "@wso2is/core/models"; +import { addAlert } from "@wso2is/core/store"; +import { useTrigger } from "@wso2is/forms"; +import { + ConfirmationModal, + GenericIconProps, + LinkButton, + PrimaryButton, + useWizardAlert +} from "@wso2is/react-components"; +import { AxiosError } from "axios"; +import intersection from "lodash-es/intersection"; +import React, { FunctionComponent, ReactElement, useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useDispatch } from "react-redux"; +import { Dispatch } from "redux"; +import { Grid, Icon, Modal } from "semantic-ui-react"; + +interface AddUserWizardPropsInterface extends IdentifiableComponentInterface, TestableComponentInterface { + closeWizard: () => void; + compact?: boolean; + currentStep?: number; + updateList?: () => void; + rolesList?: any; + emailVerificationEnabled: boolean; + requiredSteps?: WizardStepsFormTypes[] | string[]; + submitStep?: WizardStepsFormTypes | string; + showStepper?: boolean; + onSuccessfulUserAddition?: (id: string) => void; + onInvitationSendSuccessful?: () => void; + conditionallyShowStepper?: boolean; + adminTypeSelection? :string; + /** + * On user update callback. + * + * @param userId - ID of the updated user. + */ + onUserUpdate?: () => void; +} + +/** + * Interface for wizard step. + */ +interface WizardStepInterface { + content: ReactElement; + icon: GenericIconProps | any; + name: string; + title: string; +} + +/** + * Enum for wizard steps form types. + * @readonly + */ +export enum WizardStepsFormTypes { + BASIC_DETAILS = "BasicDetails" +} + +/** + * New Administrator creation wizard. + * + * @param props - Props injected to the component. + * @returns Add administrator wizard component. + */ +export const AddAdministratorWizard: FunctionComponent = ( + props: AddUserWizardPropsInterface +): ReactElement => { + + const { + adminTypeSelection, + closeWizard, + currentStep, + requiredSteps, + onInvitationSendSuccessful, + onUserUpdate, + [ "data-componentid" ]: componentId, + [ "data-testid" ]: testId + } = props; + + const { t } = useTranslation(); + const dispatch: Dispatch = useDispatch(); + + const [ submitGeneralSettings, setSubmitGeneralSettings ] = useTrigger(); + + const [ partiallyCompletedStep, setPartiallyCompletedStep ] = useState(undefined); + const [ currentWizardStep, setCurrentWizardStep ] = useState(currentStep); + const [ wizardSteps, setWizardSteps ] = useState([]); + const [ isStepsUpdated, setIsStepsUpdated ] = useState(false); + const [ isSubmitting, setIsSubmitting ] = useState(false); + const [ isFinishButtonDisabled, setFinishButtonDisabled ] = useState(false); + const [ searchQuery, setSearchQuery ] = useState(null); + const [ invite, setInvite ] = useState(null); + const [ showAdminRoleAddConfirmationModal, setShowAdminRoleAddConfirmationModal ] = useState(false); + const [ confirmationModalLoading, setConfirmationModalLoading ] = useState(false); + const [ adminRoleId, setAdminRoleId ] = useState(null); + const [ selectedUser, setSelectedUser ] = useState(null); + + const [ alert, setAlert, alertComponent ] = useWizardAlert(); + + // Excluding groups and roles from getUserList API call to improve performance. + const excludedAttributes: string = UserManagementConstants.GROUPS_ATTRIBUTE; + + const { + data: originalAdminUserList, + error: adminUserListFetchRequestError + } = useUsersList( + 1, + 0, + searchQuery, + null, + PRIMARY_USERSTORE, + excludedAttributes, + !!searchQuery + ); + + /** + * Retrieve the application data for the console application, filtering by name. + */ + const { + data: applicationListData , + error: applicationListFetchRequestError + } = useApplicationList( + null, + null, + null, + `name eq ${ApplicationManagementConstants.CONSOLE_APP_NAME}`, + !!searchQuery + ); + + /** + * Build the roles filter to search for roles specific to the console application. + */ + const roleSearchFilter: string = useMemo(() => { + if (applicationListData?.applications && applicationListData?.applications?.length > 0) { + return `audience.value eq ${applicationListData?.applications[0]?.id}`; + } + + return null; + }, [ applicationListData ]); + + /** + * Retrieve the roles list. + * Only retrieve roles when there an edge case where the user + * is already in the system and the user is not an admin user. + */ + const { + data: rolesList, + error: rolesListFetchRequestError + } = useRolesList( + null, + null, + roleSearchFilter, + null, + !!searchQuery + ); + + useEffect(() => { + setWizardSteps(filterSteps([ + WizardStepsFormTypes.BASIC_DETAILS ])); + setIsStepsUpdated(true); + }, []); + + /** + * This hook will check if the user is an admin user and prompt to add the admin role to the user. + */ + useEffect(() => { + if (!originalAdminUserList || !invite?.email) { + return; + } + + // If total results are 0, the user does not exists. + // This means there is a pending invitation for the user. + if (originalAdminUserList.totalResults <= 0) { + dispatch(addAlert({ + description: t( + "extensions:manage.invite.notifications.sendInvite.inviteAlreadyExistsError.description", + { userName: invite.email } + ), + level: AlertLevels.ERROR, + message: t( + "extensions:manage.invite.notifications.sendInvite.inviteAlreadyExistsError.message" + ) + })); + setIsSubmitting(false); + closeWizard(); + + return; + } + + // Check if the user in the first index is with Administrator role. + // If an admin, show an error message. + if (isAdminUser(originalAdminUserList.Resources[ 0 ])) { + dispatch(addAlert({ + description: t( + "extensions:manage.invite.notifications.sendInvite.userAlreadyExistsError.description", + { userName: invite.email } + ), + level: AlertLevels.ERROR, + message: t( + "extensions:manage.invite.notifications.sendInvite.userAlreadyExistsError.message" + ) + })); + setIsSubmitting(false); + closeWizard(); + } else { + // If not, prompt to assign the admin role to the user. + setIsSubmitting(false); + setShowAdminRoleAddConfirmationModal(true); + setSelectedUser(originalAdminUserList.Resources[ 0 ]); + } + }, [ originalAdminUserList ]); + + /** + * Dispatches error notifications for Admin user fetch request error. + */ + useEffect(() => { + if (!adminUserListFetchRequestError) { + return; + } + + if (adminUserListFetchRequestError?.response?.data?.description) { + dispatch(addAlert({ + description: adminUserListFetchRequestError?.response?.data?.description + ?? adminUserListFetchRequestError?.response?.data?.detail + ?? t("users:notifications.fetchUsers.error.description"), + level: AlertLevels.ERROR, + message: adminUserListFetchRequestError?.response?.data?.message + ?? t("users:notifications.fetchUsers.error.message") + })); + + return; + } + + dispatch(addAlert({ + description: t("users:notifications.fetchUsers.genericError." + + "description"), + level: AlertLevels.ERROR, + message: t("users:notifications.fetchUsers.genericError.message") + })); + }, [ adminUserListFetchRequestError ]); + + /** + * Dispatches error notifications for application list fetch request error. + */ + useEffect(() => { + if (!applicationListFetchRequestError) { + return; + } + + if (applicationListFetchRequestError.response + && applicationListFetchRequestError.response.data + && applicationListFetchRequestError.response.data.description) { + dispatch(addAlert({ + description: applicationListFetchRequestError.response.data.description, + level: AlertLevels.ERROR, + message: t("applications:notifications." + + "fetchApplications.error.message") + })); + + return; + } + + dispatch(addAlert({ + description: t("applications:notifications.fetchApplications" + + ".genericError.description"), + level: AlertLevels.ERROR, + message: t("applications:notifications." + + "fetchApplications.genericError.message") + })); + }, [ applicationListFetchRequestError ]); + + /** + * Dispatches error notifications for roles list fetch request error. + */ + useEffect(() => { + if (!rolesListFetchRequestError) { + return; + } + + if (rolesListFetchRequestError?.response?.data?.description) { + dispatch(addAlert({ + description: rolesListFetchRequestError?.response?.data?.description + ?? rolesListFetchRequestError?.response?.data?.detail + ?? t("roles:notifications.fetchRoles.error.description"), + level: AlertLevels.ERROR, + message: rolesListFetchRequestError?.response?.data?.message + ?? t("roles:notifications.fetchRoles.error.message") + })); + + return; + } + + dispatch(addAlert({ + description: t("roles:notifications.fetchRoles.genericError.description"), + level: AlertLevels.ERROR, + message: t("roles:notifications.fetchRoles.genericError.message") + })); + }, [ rolesListFetchRequestError ]); + + /** + * Get the administrator role from the roles list. + */ + useEffect(() => { + if (!rolesList || rolesList.Resources.length <= 0) { + return; + } + + const adminRole: RolesInterface = rolesList.Resources.find((role: RolesInterface) => + role.displayName === administratorConfig.adminRoleName); + + // If the admin role is not found, show an error message. + if (!adminRole) { + dispatch(addAlert({ + description: t( + "extensions:manage.users.wizard.addAdmin.internal.noAdminRoleError.description" + ), + level: AlertLevels.ERROR, + message: t( + "extensions:manage.users.wizard.addAdmin.internal.noAdminRoleError.message" + ) + })); + } else { + // Else, set the admin role ID. + setAdminRoleId(adminRole?.id); + } + }, [ rolesList ]); + + /** + * Sets the current wizard step to the previous on every `partiallyCompletedStep` + * value change, and resets the partially completed step value. + */ + useEffect(() => { + if (partiallyCompletedStep === undefined) { + return; + } + + setCurrentWizardStep(currentWizardStep - 1); + setPartiallyCompletedStep(undefined); + }, [ partiallyCompletedStep ]); + + /** + * Navigates to the next step. + */ + const navigateToNext = () => { + switch (wizardSteps[ currentWizardStep ]?.name) { + case WizardStepsFormTypes.BASIC_DETAILS: + setSubmitGeneralSettings(); + + break; + default: + break; + } + }; + + /** + * Navigates to the previous step. + */ + const navigateToPrevious = () => { + setPartiallyCompletedStep(currentWizardStep); + }; + + /** + * This function handles assigning the roles to the user. + */ + const assignUserRoles = async (users: UserBasicInterface[], roles: RolesInterface[]) => { + const roleIds: string[] = []; + const userList: {display: string, value: string}[] = []; + + if (users.length > 0) { + users.map((user: UserBasicInterface) => { + userList.push({ + display: user.userName, + value: user.id + }); + }); + } + + // Payload for the update role request. + const roleData: PatchRoleDataInterface = { + Operations: [ + { + op: "add", + value: { + users: userList + } + } + ], + schemas: [ "urn:ietf:params:scim:api:messages:2.0:PatchOp" ] + }; + + if (roles.length > 0) { + roles.map((role: RolesInterface) => { + roleIds.push(role.id); + }); + + for (const roleId of roleIds) { + setIsSubmitting(true); + + await updateRoleDetails(roleId, roleData) + .catch((error: AxiosError) => { + if (!error.response || error.response.status === 401) { + setAlert({ + description: t( + "extensions:manage.users.wizard.addAdmin.internal.updateRole.error.description" + ), + level: AlertLevels.ERROR, + message: t( + "extensions:manage.users.wizard.addAdmin.internal.updateRole.error.message" + ) + }); + } else if (error.response && error.response.data && error.response.data.detail) { + setAlert({ + description: t( + "extensions:manage.users.wizard.addAdmin.internal.updateRole.error.description", + { description: error.response.data.detail } + ), + level: AlertLevels.ERROR, + message: t( + "extensions:manage.users.wizard.addAdmin.internal.updateRole.error.message" + ) + }); + } else { + // Generic error message + setAlert({ + description: t( + "extensions:manage.users.wizard.addAdmin.internal.updateRole.genericError" + + ".description" + ), + level: AlertLevels.ERROR, + message: t( + "extensions:manage.users.wizard.addAdmin.internal.updateRole.genericError.message" + ) + }); + } + }) + .finally(() => { + setIsSubmitting(false); + }); + } + dispatch(addAlert({ + description: t( + "extensions:manage.users.wizard.addAdmin.internal.updateRole.success.description" + ), + level: AlertLevels.SUCCESS, + message: t( + "extensions:manage.users.wizard.addAdmin.internal.updateRole.success.message" + ) + })); + closeWizard(); + onUserUpdate(); + } + }; + + /** + * Assigns the admin role to the user. + */ + const assignAdminRole = () => { + if (!selectedUser || !adminRoleId) { + return; + } + + // Payload for the update role request. + const roleData: PatchRoleDataInterface = { + Operations: [ + { + op: "add", + value: { + users: [ + { + display: selectedUser?.userName, + value: selectedUser?.id + } + ] + } + } + ], + schemas: [ "urn:ietf:params:scim:api:messages:2.0:PatchOp" ] + }; + + setConfirmationModalLoading(true); + + updateRoleDetails(adminRoleId, roleData) + .then(() => { + dispatch(addAlert({ + description: t( + "extensions:manage.users.wizard.addAdmin.internal.updateRole.success.description" + ), + level: AlertLevels.SUCCESS, + message: t( + "extensions:manage.users.wizard.addAdmin.internal.updateRole.success.message" + ) + })); + onUserUpdate(); + }) + .catch((error: AxiosError) => { + if (!error.response || error.response.status === 401) { + setAlert({ + description: t( + "extensions:manage.users.wizard.addAdmin.internal.updateRole.error.description" + ), + level: AlertLevels.ERROR, + message: t( + "extensions:manage.users.wizard.addAdmin.internal.updateRole.error.message" + ) + }); + } else if (error.response && error.response.data && error.response.data.detail) { + setAlert({ + description: t( + "extensions:manage.users.wizard.addAdmin.internal.updateRole.error.description", + { description: error.response.data.detail } + ), + level: AlertLevels.ERROR, + message: t( + "extensions:manage.users.wizard.addAdmin.internal.updateRole.error.message" + ) + }); + } else { + // Generic error message + setAlert({ + description: t( + "extensions:manage.users.wizard.addAdmin.internal.updateRole.genericError.description" + ), + level: AlertLevels.ERROR, + message: t( + "extensions:manage.users.wizard.addAdmin.internal.updateRole.genericError.message" + ) + }); + } + }) + .finally(() => { + setIsSubmitting(false); + setConfirmationModalLoading(false); + closeWizard(); + }); + }; + + /** + * This function handles sending the invitation to the external admin user. + */ + const sendExternalInvitation = (invite: UserInviteInterface) => { + if (invite != null) { + setIsSubmitting(true); + + sendInvite(invite) + .then(() => { + dispatch(addAlert({ + description: t( + "console:manage.features.invite.notifications.sendInvite.success.description" + ), + level: AlertLevels.SUCCESS, + message: t( + "console:manage.features.invite.notifications.sendInvite.success.message" + ) + })); + setIsSubmitting(false); + closeWizard(); + onInvitationSendSuccessful(); + }) + .catch((error: AxiosError) => { + // Axios throws a generic `Network Error` for 401 status. + // As a temporary solution, a check to see if a response + // is available has be used. + if (!error.response || error.response.status === 401) { + setIsSubmitting(false); + closeWizard(); + dispatch(addAlert({ + description: t( + "console:manage.features.invite.notifications.sendInvite.error.description" + ), + level: AlertLevels.ERROR, + message: t( + "console:manage.features.invite.notifications.sendInvite.error.message" + ) + })); + } else if (error.response.status === 403 && + error?.response?.data?.code === UserManagementConstants.ERROR_COLLABORATOR_USER_LIMIT_REACHED) { + setIsSubmitting(false); + closeWizard(); + dispatch(addAlert({ + description: t( + "extensions:manage.invite.notifications.sendInvite.limitReachError.description" + ), + level: AlertLevels.ERROR, + message: t( + "extensions:manage.invite.notifications.sendInvite.limitReachError.message" + ) + })); + } else if (error.response.status === 409) { + // User already exists in the system. + // We need to check if this user is having the Administator role. + handleAlredyExistingUser(invite); + } else if (error?.response?.data?.description) { + setIsSubmitting(false); + closeWizard(); + dispatch(addAlert({ + description: t( + "console:manage.features.invite.notifications.sendInvite.error.description", + { description: error.response.data.description } + ), + level: AlertLevels.ERROR, + message: t( + "console:manage.features.invite.notifications.sendInvite.error.message" + ) + })); + } else { + setIsSubmitting(false); + closeWizard(); + // Generic error message + dispatch(addAlert({ + description: t( + "console:manage.features.invite.notifications.sendInvite.genericError.description" + ), + level: AlertLevels.ERROR, + message: t( + "console:manage.features.invite.notifications.sendInvite.genericError.message" + ) + })); + } + }); + } + }; + + /** + * Handles the already existing user. + * + * @param error - Axios error response. + */ + const handleAlredyExistingUser = (invite: UserInviteInterface): void => { + if (!invite?.email) { + setIsSubmitting(false); + + return; + } + + if (!selectedUser || selectedUser?.userName !== invite.email) { + // If the user is not selected. Query the user. + setSearchQuery(`userName eq ${ invite.email }`); + setInvite(invite); + } else { + // If the user is already selected, prompt to assign the admin role. + // Handles cancelling and re-opening the modal. + setShowAdminRoleAddConfirmationModal(true); + setIsSubmitting(false); + } + }; + + /** + * This function handles sending the invitation to the external admin user. + */ + const sendInternalInvitation = (data: InternalAdminFormDataInterface) => { + assignUserRoles(data?.checkedUsers, data?.selectedRoles); + }; + + /** + * Resolves the step content. + * + * @returns Step content. + */ + const resolveStepContent = (): ReactElement => { + switch (wizardSteps[ currentWizardStep ]?.name) { + case WizardStepsFormTypes.BASIC_DETAILS: + return getAdminBasicDetailsWizardStep()?.content; + } + }; + + /** + * Filters the steps evaluating the requested steps. + * + * @param steps - Steps to filter. + * @returns Filtered steps. + */ + const filterSteps = (steps: WizardStepsFormTypes[]): WizardStepInterface[] => { + + const getStepContent = (stepsToFilter: WizardStepsFormTypes[] | string[]) => { + + const filteredSteps: any[] = []; + + stepsToFilter.forEach((step: WizardStepsFormTypes) => { + if (step === WizardStepsFormTypes.BASIC_DETAILS) { + filteredSteps.push(getAdminBasicDetailsWizardStep()); + } + }); + + return filteredSteps; + }; + + if (!requiredSteps) { + return getStepContent(steps); + } + + return getStepContent(intersection(steps, requiredSteps)); + }; + + /** + * Basic Wizard Step. + * @returns Basic details wizard step. + */ + const getAdminBasicDetailsWizardStep = (): WizardStepInterface => { + + return { + content: ( + + (adminTypeSelection === AdminAccountTypes.INTERNAL) + ? sendInternalInvitation(values as InternalAdminFormDataInterface) + : sendExternalInvitation(values as UserInviteInterface) + } + setFinishButtonDisabled={ setFinishButtonDisabled } + /> + ), + icon: getUserWizardStepIcons().general, + name: WizardStepsFormTypes.BASIC_DETAILS, + title: t("console:manage.features.user.modals.addUserWizard.steps.basicDetails") + }; + }; + + return ( + wizardSteps && isStepsUpdated ? ( + <> + + + { adminTypeSelection === AdminAccountTypes.INTERNAL + ? t("extensions:manage.users.wizard.addAdmin.internal.title") + : t("extensions:manage.users.wizard.addAdmin.external.title") + } + + + { alert && alertComponent } + { resolveStepContent() } + + + + + + closeWizard() } + > + { t("common:cancel") } + + + + { (currentWizardStep < wizardSteps?.length - 1) && ( + + { t("console:manage.features.user.modals.addUserWizard.buttons.next") } + + + ) } + { currentWizardStep === wizardSteps?.length - 1 && ( + + { + adminTypeSelection === AdminAccountTypes.INTERNAL + ? t("extensions:manage.features.user.addUser.add") + : t("extensions:manage.features.user.addUser.invite") + } + + ) } + { (wizardSteps?.length > 1 && currentWizardStep > 0) && ( + + + { t("console:manage.features.user.modals.addUserWizard.buttons.previous") } + + ) } + + + + + + { + selectedUser && adminRoleId && ( + { + setSubmitGeneralSettings(); + setShowAdminRoleAddConfirmationModal(false); + } } + type="warning" + open={ showAdminRoleAddConfirmationModal } + assertionHint={ + t("extensions:manage.invite.assignAdminUser.confirmationModal.assertionHint") } + assertionType="checkbox" + primaryAction="Confirm" + secondaryAction="Cancel" + onSecondaryActionClick={ (): void => { + setSubmitGeneralSettings(); + setShowAdminRoleAddConfirmationModal(false); + setAlert(null); + } } + onPrimaryActionClick={ (): void => { + setConfirmationModalLoading(true); + assignAdminRole(); + } } + closeOnDimmerClick={ false } + > + + { t("extensions:manage.invite.assignAdminUser.confirmationModal.header") } + + + { t("extensions:manage.invite.assignAdminUser.confirmationModal.message") } + + + ) + } + + ) : null + ); +}; + +/** + * Default props for the add administrator wizard. + */ +AddAdministratorWizard.defaultProps = { + compact: false, + conditionallyShowStepper: false, + currentStep: 0, + emailVerificationEnabled: false, + showStepper: true, + submitStep: WizardStepsFormTypes.BASIC_DETAILS +}; diff --git a/features/admin.console-settings.v1/components/console-administrators/administrators-list/administrators-list.tsx b/features/admin.console-settings.v1/components/console-administrators/administrators-list/administrators-list.tsx index f3f61157899..871bbfbf18b 100644 --- a/features/admin.console-settings.v1/components/console-administrators/administrators-list/administrators-list.tsx +++ b/features/admin.console-settings.v1/components/console-administrators/administrators-list/administrators-list.tsx @@ -26,7 +26,6 @@ import { import { useOrganizationConfigV2 } from "@wso2is/admin.administrators.v1/api/useOrganizationConfigV2"; import { AdministratorConstants } from "@wso2is/admin.administrators.v1/constants/users"; import { UseOrganizationConfigType } from "@wso2is/admin.administrators.v1/models/organization"; -import { AddAdministratorWizard } from "@wso2is/admin.administrators.v1/wizard/add-administrator-wizard"; import { AdvancedSearchWithBasicFilters, AppConstants, @@ -64,6 +63,7 @@ import AdministratorsTable from "./administrators-table"; import useAdministrators from "../../../hooks/use-administrators"; import useBulkAssignAdministratorRoles from "../../../hooks/use-bulk-assign-user-roles"; import AddExistingUserWizard from "../add-existing-user-wizard/add-existing-user-wizard"; +import { AddAdministratorWizard } from "../add-external-admin-wizard/add-external-admin-wizard"; import InviteNewAdministratorWizard from "../invite-new-administrator-wizard/invite-new-administrator-wizard"; /** diff --git a/features/admin.console-settings.v1/components/console-administrators/console-administrators.tsx b/features/admin.console-settings.v1/components/console-administrators/console-administrators.tsx index f11dfcc9864..e00a377d271 100644 --- a/features/admin.console-settings.v1/components/console-administrators/console-administrators.tsx +++ b/features/admin.console-settings.v1/components/console-administrators/console-administrators.tsx @@ -186,8 +186,8 @@ const ConsoleAdministrators: FunctionComponent = value={ activeAdministratorGroup } onChange={ (_: ChangeEvent, value: string) => setActiveAdministratorGroup(value) } > - } label="Administrators" /> - } label="Privileged Users" /> + } label="Asgardeo" /> + } label={ organizationName + " organization" } /> ); } diff --git a/features/admin.console-settings.v1/components/console-roles/console-roles-edit/console-roles-edit.scss b/features/admin.console-settings.v1/components/console-roles/console-roles-edit/console-roles-edit.scss index c4772fc655a..43dbbc3d7d7 100644 --- a/features/admin.console-settings.v1/components/console-roles/console-roles-edit/console-roles-edit.scss +++ b/features/admin.console-settings.v1/components/console-roles/console-roles-edit/console-roles-edit.scss @@ -6,4 +6,9 @@ .submit-button { margin-top: 2rem; } + + .multi-option-radio-group { + gap: 40px; + margin: 20px 0; + } } diff --git a/features/admin.console-settings.v1/components/console-roles/console-roles-edit/console-roles-edit.tsx b/features/admin.console-settings.v1/components/console-roles/console-roles-edit/console-roles-edit.tsx index d96315bb968..43a9ec81bb9 100644 --- a/features/admin.console-settings.v1/components/console-roles/console-roles-edit/console-roles-edit.tsx +++ b/features/admin.console-settings.v1/components/console-roles/console-roles-edit/console-roles-edit.tsx @@ -30,11 +30,14 @@ import { RoleAudienceTypes } from "@wso2is/admin.roles.v2/constants/role-constan import { RoleConstants } from "@wso2is/core/constants"; import { FeatureAccessConfigInterface, IdentifiableComponentInterface, RolesInterface } from "@wso2is/core/models"; import { ResourceTab, ResourceTabPaneInterface } from "@wso2is/react-components"; -import React, { FunctionComponent, ReactElement, useEffect, useState } from "react"; +import React, { ChangeEvent, FunctionComponent, ReactElement, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { useSelector } from "react-redux"; import ConsoleRolePermissions from "./console-role-permissions"; import "./console-roles-edit.scss"; +import RadioGroup from "@oxygen-ui/react/RadioGroup"; +import { FormControlLabel } from "@mui/material"; +import Radio from "@oxygen-ui/react/Radio"; /** * Captures props needed for edit role component @@ -64,40 +67,41 @@ interface ConsoleRolesEditPropsInterface extends IdentifiableComponentInterface * @param props - contains role details to be edited. */ const ConsoleRolesEdit: FunctionComponent = ( - props: ConsoleRolesEditPropsInterface): ReactElement => { - - const { - isLoading, - roleObject, - onRoleUpdate, - defaultActiveIndex - } = props; + props: ConsoleRolesEditPropsInterface +): ReactElement => { + const { isLoading, roleObject, onRoleUpdate, defaultActiveIndex } = props; const { t } = useTranslation(); const { isFirstLevelOrganization, isSubOrganization, organizationType } = useGetCurrentOrganizationType(); const userRolesFeatureConfig: FeatureAccessConfigInterface = useSelector( - (state: AppState) => state?.config?.ui?.features?.userRoles); + (state: AppState) => state?.config?.ui?.features?.userRoles + ); const hasRolesUpdatePermissions: boolean = useRequiredScopes(userRolesFeatureConfig?.scopes?.update); const administratorRoleDisplayName: string = useSelector( - (state: AppState) => state?.config?.ui?.administratorRoleDisplayName); + (state: AppState) => state?.config?.ui?.administratorRoleDisplayName + ); - const consoleSettingsFeatureConfig: FeatureAccessConfigInterface = - useSelector((state: AppState) => state?.config?.ui?.features?.consoleSettings); + const consoleSettingsFeatureConfig: FeatureAccessConfigInterface = useSelector( + (state: AppState) => state?.config?.ui?.features?.consoleSettings + ); const isConsoleRolesEditable: boolean = !consoleSettingsFeatureConfig?.disabledFeatures?.includes( "consoleSettings.editableConsoleRoles" ); + const isPrivilegedUsersInConsoleSettingsEnabled: boolean = !consoleSettingsFeatureConfig?.disabledFeatures?.includes( + "consoleSettings.privilegedUsers" + ); const [ isAdminRole, setIsAdminRole ] = useState(false); const [ isEnterpriseLoginEnabled, setIsEnterpriseLoginEnabled ] = useState(false); + const [ activeUserStore, setActiveUserStore ] = useState("PRIMARY"); const organizationName: string = store.getState().auth.tenantDomain; const useOrgConfig: UseOrganizationConfigType = useOrganizationConfigV2; - const saasFeatureStatus : FeatureStatus = useCheckFeatureStatus( - FeatureGateConstants.SAAS_FEATURES_IDENTIFIER); + const saasFeatureStatus: FeatureStatus = useCheckFeatureStatus(FeatureGateConstants.SAAS_FEATURES_IDENTIFIER); const { data: OrganizationConfig, @@ -121,10 +125,12 @@ const ConsoleRolesEdit: FunctionComponent = ( * Set the if the role is `Internal/admin`. */ useEffect(() => { - if(roleObject) { - setIsAdminRole(roleObject.displayName === RoleConstants.ADMIN_ROLE || - roleObject?.displayName === RoleConstants.ADMIN_GROUP || - roleObject?.displayName === administratorRoleDisplayName); + if (roleObject) { + setIsAdminRole( + roleObject.displayName === RoleConstants.ADMIN_ROLE || + roleObject?.displayName === RoleConstants.ADMIN_GROUP || + roleObject?.displayName === administratorRoleDisplayName + ); } }, [ roleObject ]); @@ -135,8 +141,9 @@ const ConsoleRolesEdit: FunctionComponent = ( render: () => ( = ( = ( ) }, - ( (isFirstLevelOrganization() && isEnterpriseLoginEnabled) || isSubOrganization() ) && { + ((isFirstLevelOrganization() && isEnterpriseLoginEnabled) || isSubOrganization()) && { menuItem: t("roles:edit.menuItems.groups"), render: () => ( @@ -181,10 +185,34 @@ const ConsoleRolesEdit: FunctionComponent = ( menuItem: t("roles:edit.menuItems.users"), render: () => ( + { isFirstLevelOrganization() && + isEnterpriseLoginEnabled && + isPrivilegedUsersInConsoleSettingsEnabled && ( + , value: string) => { + setActiveUserStore(value); + } } + > + } label="Asgardeo" /> + } + label={ organizationName + " organization" } + /> + + ) } + @@ -211,13 +239,7 @@ const ConsoleRolesEdit: FunctionComponent = ( return panes; }; - return ( - - ); + return ; }; ConsoleRolesEdit.defaultProps = { diff --git a/features/admin.roles.v2/components/edit-role/edit-role-users.tsx b/features/admin.roles.v2/components/edit-role/edit-role-users.tsx index 356c53b1892..3d799d2b181 100644 --- a/features/admin.roles.v2/components/edit-role/edit-role-users.tsx +++ b/features/admin.roles.v2/components/edit-role/edit-role-users.tsx @@ -81,6 +81,7 @@ export const RoleUsersList: FunctionComponent = ( onRoleUpdate, isReadOnly, tabIndex, + activeUserStore, [ "data-componentid" ]: componentId } = props; @@ -97,7 +98,7 @@ export const RoleUsersList: FunctionComponent = ( const [ activeOption, setActiveOption ] = useState(undefined); const [ availableUserStores, setAvailableUserStores ] = useState([]); const [ selectedUserStoreDomainName, setSelectedUserStoreDomainName ] = useState( - disabledUserstores.includes(RemoteUserStoreConstants.PRIMARY_USER_STORE_NAME) ? "DEFAULT" : "Primary" + activeUserStore ?? disabledUserstores.includes(RemoteUserStoreConstants.PRIMARY_USER_STORE_NAME) ? "DEFAULT" : "PRIMARY" ); const [ isPlaceholderVisible, setIsPlaceholderVisible ] = useState(true); const [ selectedUsersFromUserStore, setSelectedUsersFromUserStore ] = useState([]); @@ -125,10 +126,14 @@ export const RoleUsersList: FunctionComponent = ( const isUserBelongToSelectedUserStore = (user: UserBasicInterface, userStoreName: string) => { const userNameChunks: string[] = user.userName.split("/"); - return (userNameChunks.length === 1 && userStoreName === "Primary") + return (userNameChunks.length === 1 && userStoreName === "PRIMARY") || (userNameChunks.length === 2 && userNameChunks[0] === userStoreName.toUpperCase()); }; + useEffect(() => { + setSelectedUserStoreDomainName(activeUserStore); + }, [ activeUserStore ]); + useEffect(() => { if (!role?.users?.length) { return; @@ -189,6 +194,7 @@ export const RoleUsersList: FunctionComponent = ( if (!isReadOnly && userResponse?.totalResults > 0 && Array.isArray(userResponse?.Resources)) { const usersAvailableToSelect: UserBasicInterface[] = userResponse?.Resources?.filter( (user: UserBasicInterface) => { + console.log(selectedUserStoreDomainName, user); const isUserInSelectedUserStore: boolean = isUserBelongToSelectedUserStore( user, selectedUserStoreDomainName ); @@ -295,6 +301,8 @@ export const RoleUsersList: FunctionComponent = ( path: "/v2/Roles/" + role.id }; + debugger; + const selectedUsers: UserBasicInterface[] = Object.keys(selectedAllUsers).map((userStoreName: string) => { return selectedAllUsers[userStoreName]; }).flat(); @@ -303,8 +311,16 @@ export const RoleUsersList: FunctionComponent = ( const removedUsers: RolesMemberInterface[] = role?.users?.filter((user: RolesMemberInterface) => { return selectedUsers?.find( (selectedUser: UserBasicInterface) => selectedUser.id === user.value) === undefined; + }).filter(user => { + console.log(user); + const userNameChunks: string[] = user.display.split("/"); + + return (userNameChunks.length === 1 && selectedUserStoreDomainName === "PRIMARY") + || (userNameChunks.length === 2 && userNameChunks[0] === selectedUserStoreDomainName.toUpperCase()); }) ?? []; + + const removeOperations: PatchUserRemoveOpInterface[] = removedUsers?.map((user: RolesMemberInterface) => { return ({ "op": "remove", @@ -390,33 +406,35 @@ export const RoleUsersList: FunctionComponent = ( { users && availableUserStores && !isReadOnly && ( - - - ) => { + updateSelectedAllUsers(); + setSelectedUsersFromUserStore([]); + setSelectedUserStoreDomainName(e.target.value as string); + } } - } - data-componentid={ `${ componentId }-user-store-domain-dropdown` } - > - { isUserStoresLoading - ?

{ t("common:loading") }

- : availableUserStores?.map((userstore: UserStoreListItem) => - ( - { userstore.name } - ) - ) - } - -
-
+ data-componentid={ `${ componentId }-user-store-domain-dropdown` } + > + { isUserStoresLoading + ?

{ t("common:loading") }

+ : availableUserStores?.map((userstore: UserStoreListItem) => + ( + { userstore.name } + ) + ) + } + + +
+ ) }