diff --git a/openmetadata-ui/src/main/resources/ui/src/components/TeamDetails/TeamDetails.tsx b/openmetadata-ui/src/main/resources/ui/src/components/TeamDetails/TeamDetails.tsx index ecc9adac708..58cdb829ed2 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/TeamDetails/TeamDetails.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/TeamDetails/TeamDetails.tsx @@ -17,10 +17,11 @@ import { compare } from 'fast-json-patch'; import { cloneDeep, orderBy } from 'lodash'; import { ExtraInfo, TableDetail } from 'Models'; import React, { Fragment, useEffect, useState } from 'react'; -import { Link } from 'react-router-dom'; +import { Link, useHistory } from 'react-router-dom'; import AppState from '../../AppState'; import { getTeamAndUserDetailsPath, + getUserPath, PAGE_SIZE_MEDIUM, TITLE_FOR_NON_ADMIN_ACTION, } from '../../constants/constants'; @@ -45,6 +46,7 @@ import NextPrevious from '../common/next-previous/NextPrevious'; import NonAdminAction from '../common/non-admin-action/NonAdminAction'; import Searchbar from '../common/searchbar/Searchbar'; import TabsPane from '../common/TabsPane/TabsPane'; +import Loader from '../Loader/Loader'; import ManageTab from '../ManageTab/ManageTab.component'; import ConfirmationModal from '../Modals/ConfirmationModal/ConfirmationModal'; import FormModal from '../Modals/FormModal'; @@ -61,6 +63,7 @@ const TeamDetails = ({ isDescriptionEditable, errorNewTeamData, isAddingTeam, + isTeamMemberLoading, handleAddTeam, createNewTeam, onNewTeamDataChange, @@ -76,6 +79,7 @@ const TeamDetails = ({ handleAddUser, removeUserFromTeam, }: TeamDetailsProp) => { + const history = useHistory(); const DELETE_USER_INITIAL_STATE = { user: undefined, state: false, @@ -127,7 +131,11 @@ const TeamDetails = ({ { name: 'Manage', isProtected: false, - isHidden: isOwner() || userPermissions[Operation.UpdateOwner], + isHidden: !( + hasAccess || + isOwner() || + userPermissions[Operation.UpdateOwner] + ), position: 4, }, ]; @@ -218,6 +226,14 @@ const TeamDetails = ({ return Promise.reject(); }; + /** + * Redirects user to profile page. + * @param name user name + */ + const handleUserRedirection = (name: string) => { + history.push(getUserPath(name)); + }; + useEffect(() => { if (currentTeam) { setHeading(currentTeam.displayName); @@ -284,64 +300,71 @@ const TeamDetails = ({ )} - {currentTeamUsers.length <= 0 ? ( -
-

- There are no users{' '} - {teamUsersSearchText - ? `as ${teamUsersSearchText}.` - : `added yet.`} -

- {isActionAllowed(userPermissions[Operation.UpdateTeam]) ? ( - <> -

Would like to start adding some?

- - - ) : null} -
+ {isTeamMemberLoading ? ( + ) : ( - -
- {sortedUser.map((user, index) => { - const User = { - displayName: user.displayName || user.name, - fqn: user.name || '', - type: 'user', - id: user.id, - name: user.name, - }; +
+ {currentTeamUsers.length <= 0 ? ( +
+

+ There are no users{' '} + {teamUsersSearchText + ? `as ${teamUsersSearchText}.` + : `added yet.`} +

+ {isActionAllowed(userPermissions[Operation.UpdateTeam]) ? ( + <> +

Would like to start adding some?

+ + + ) : null} +
+ ) : ( + +
+ {sortedUser.map((user, index) => { + const User = { + displayName: user.displayName || user.name, + fqn: user.name || '', + type: 'user', + id: user.id, + name: user.name, + }; - return ( - + ); + })} +
+ {teamUserPagin.total > PAGE_SIZE_MEDIUM && ( + - ); - })} -
- {teamUserPagin.total > PAGE_SIZE_MEDIUM && ( - + )} + )} - +
)} ); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/TeamsAndUsers/TeamsAndUsers.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/TeamsAndUsers/TeamsAndUsers.component.tsx index d54130cc3e9..ea1821a1b0f 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/TeamsAndUsers/TeamsAndUsers.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/TeamsAndUsers/TeamsAndUsers.component.tsx @@ -18,20 +18,22 @@ import { TeamsAndUsersProps } from '../../interface/teamsAndUsers.interface'; import { getActiveCatClass, getCountBadge } from '../../utils/CommonUtils'; import { getActiveUsers } from '../../utils/TeamUtils'; import PageLayout from '../containers/PageLayout'; +import Loader from '../Loader/Loader'; import TeamDetails from '../TeamDetails/TeamDetails'; import UserDetails from '../UserDetails/UserDetails'; const TeamsAndUsers = ({ users, + isUsersLoading, admins, bots, activeUserTab, userSearchTerm, selectedUserList, - updateUser, handleUserSearchTerm, handleDeleteUser, handleJoinTeamClick, + isRightPannelLoading, hasAccess, isTeamVisible, teams, @@ -54,6 +56,7 @@ const TeamsAndUsers = ({ teamUserPaginHandler, changeCurrentTeam, isAddingUsers, + isTeamMemberLoading, getUniqueUserList, addUsersToTeam, handleAddUser, @@ -145,47 +148,52 @@ const TeamsAndUsers = ({ return ( -
- {!isTeamVisible ? ( - - ) : ( - - )} -
+ {isRightPannelLoading ? ( + + ) : ( +
+ {!isTeamVisible ? ( + + ) : ( + + )} +
+ )}
); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/UserDataCard/UserDataCard.tsx b/openmetadata-ui/src/main/resources/ui/src/components/UserDataCard/UserDataCard.tsx index 43f41b472bd..4df7304854f 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/UserDataCard/UserDataCard.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/UserDataCard/UserDataCard.tsx @@ -23,18 +23,19 @@ type Item = { name: string; id?: string; email: string; - isActiveUser: boolean; - profilePhoto: string; - teamCount: string; + isActiveUser?: boolean; + profilePhoto?: string; + teamCount?: string; }; type Props = { item: Item; - onClick: (value: string) => void; + showTeams?: boolean; + onClick?: (value: string) => void; onDelete?: (id: string, name: string) => void; }; -const UserDataCard = ({ item, onClick, onDelete }: Props) => { +const UserDataCard = ({ item, onClick, onDelete, showTeams = true }: Props) => { return (
{

{ - onClick(item.id as string); + onClick?.(item.name); }}> {item.description}

- {!item.isActiveUser && ( + {!item?.isActiveUser && ( Inactive )}

{item.email}

-

Teams: {item.teamCount}

+ {showTeams &&

Teams: {item.teamCount}

}
{!isNil(onDelete) && ( diff --git a/openmetadata-ui/src/main/resources/ui/src/components/UserDetails/UserDetails.tsx b/openmetadata-ui/src/main/resources/ui/src/components/UserDetails/UserDetails.tsx index 20dea620f69..9c3957d12ea 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/UserDetails/UserDetails.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/UserDetails/UserDetails.tsx @@ -11,27 +11,28 @@ * limitations under the License. */ -import { compare, Operation } from 'fast-json-patch'; import { isUndefined } from 'lodash'; -import React, { useEffect, useState } from 'react'; -import AppState from '../../AppState'; -import { TITLE_FOR_NON_ADMIN_ACTION } from '../../constants/constants'; -import { Role } from '../../generated/entity/teams/role'; +import React, { useState } from 'react'; +import { useHistory } from 'react-router-dom'; +import { + getUserPath, + TITLE_FOR_NON_ADMIN_ACTION, +} from '../../constants/constants'; import { EntityReference, User } from '../../generated/entity/teams/user'; import { getEntityName } from '../../utils/CommonUtils'; import { Button } from '../buttons/Button/Button'; import ErrorPlaceHolder from '../common/error-with-placeholder/ErrorPlaceHolder'; import NonAdminAction from '../common/non-admin-action/NonAdminAction'; import Searchbar from '../common/searchbar/Searchbar'; +import Loader from '../Loader/Loader'; import ConfirmationModal from '../Modals/ConfirmationModal/ConfirmationModal'; -import UserDetailsModal from '../Modals/UserDetailsModal/UserDetailsModal'; import UserDataCard from '../UserDataCard/UserDataCard'; type UserDetailsProps = { selectedUserList: User[]; handleUserSearchTerm: (value: string) => void; userSearchTerm: string; - updateUser: (id: string, data: Operation[], updatedUser: User) => void; + isUsersLoading: boolean; handleDeleteUser: (id: string) => void; handleAddNewUser: () => void; }; @@ -44,45 +45,14 @@ interface DeleteUserInfo { const UserDetails = ({ selectedUserList, userSearchTerm, + isUsersLoading, handleDeleteUser, handleUserSearchTerm, - updateUser, handleAddNewUser, }: UserDetailsProps) => { - const [selectedUser, setSelectedUser] = useState(); - const [roles, setRoles] = useState([]); + const history = useHistory(); const [deletingUser, setDeletingUser] = useState(); - const selectUser = (id: string) => { - const user = selectedUserList.find((user) => user.id === id); - if (user) { - setSelectedUser(user); - } else { - setSelectedUser(undefined); - } - }; - - const handleSave = (rolesData: Array) => { - if (selectedUser) { - const updatedData: User = { - ...selectedUser, - isAdmin: Boolean(rolesData.find((role) => role === 'admin')), - roles: roles - .filter((role) => rolesData.includes(role.id)) - .map((role) => ({ - id: role.id, - type: 'role', - href: role.href, - displayName: role.displayName, - })), - }; - const jsonPatch = compare(selectedUser, updatedData); - updateUser(selectedUser.id, jsonPatch, updatedData); - - setSelectedUser(undefined); - } - }; - const handleDeleteUserModal = (id: string, name: string) => { setDeletingUser({ name, @@ -90,18 +60,24 @@ const UserDetails = ({ }); }; + /** + * Redirects user to profile page. + * @param name user name + */ + const handleUserRedirection = (name: string) => { + history.push(getUserPath(name)); + }; + const onConfirmDeleteUser = (id: string) => { handleDeleteUser(id); setDeletingUser(undefined); }; - useEffect(() => { - setRoles(AppState.userRoles); - }, [AppState.userRoles]); - const getUserCards = () => { - return ( - <> + return isUsersLoading ? ( + + ) : ( +
{selectedUserList.length > 0 ? (
selectUser(User.id)}> +
@@ -141,7 +114,7 @@ const UserDetails = ({

No user available

)} - +
); }; @@ -173,16 +146,6 @@ const UserDetails = ({
{getUserCards()} - {!isUndefined(selectedUser) && ( - setSelectedUser(undefined)} - onSave={handleSave} - /> - )} - {!isUndefined(deletingUser) && ( void; @@ -42,9 +44,9 @@ export interface TeamsAndUsersProps { currentTeamUserPage: number; teamUsersSearchText: string; isDescriptionEditable: boolean; + isRightPannelLoading: boolean; errorNewTeamData: FormErrorData | undefined; isAddingTeam: boolean; - updateUser: (id: string, data: Operation[], updatedUser: User) => void; createNewTeam: (data: Team) => void; handleAddTeam: (value: boolean) => void; onNewTeamDataChange: ( @@ -82,6 +84,7 @@ export interface TeamDetailsProp { currentTeamUserPage: number; teamUsersSearchText: string; isDescriptionEditable: boolean; + isTeamMemberLoading: boolean; hasAccess: boolean; errorNewTeamData: FormErrorData | undefined; isAddingTeam: boolean; diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/TeamsAndUsersPage/TeamsAndUsersPage.component.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/TeamsAndUsersPage/TeamsAndUsersPage.component.tsx index d2aa73860d1..e70db988143 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/TeamsAndUsersPage/TeamsAndUsersPage.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/TeamsAndUsersPage/TeamsAndUsersPage.component.tsx @@ -61,7 +61,10 @@ const TeamsAndUsersPage = () => { const { isAuthDisabled } = useAuthContext(); const history = useHistory(); const [isLoading, setIsLoading] = useState(true); + const [isRightPannelLoading, setIsRightPannelLoading] = useState(true); + const [isTeamMemberLoading, setIsTeamMemberLoading] = useState(true); const [isTeamVisible, setIsTeamVisible] = useState(true); + const [isUsersLoading, setIsUsersLoading] = useState(true); const [teams, setTeams] = useState([]); const [currentTeam, setCurrentTeam] = useState(); const [currentTeamUsers, setCurrentTeamUsers] = useState([]); @@ -84,6 +87,10 @@ const TeamsAndUsersPage = () => { setIsDescriptionEditable(value); }; + const handleRightPannelLoading = (value: boolean) => { + setIsRightPannelLoading(value); + }; + const handleAddTeam = (value: boolean) => { setIsAddingTeam(value); }; @@ -101,6 +108,7 @@ const TeamsAndUsersPage = () => { }; const setAllTabList = (users: User[], type = '') => { + setIsUsersLoading(true); const dBots = users.filter((user) => user.isBot); const dUsers = users.filter((user) => !user.isBot); const dAdmins = users.filter((user) => user.isAdmin); @@ -126,9 +134,11 @@ const TeamsAndUsersPage = () => { break; } } + setIsUsersLoading(false); }; const handleUserSearchTerm = (value: string) => { + setIsUsersLoading(true); setUserSearchTerm(value); if (value) { let updatedList: User[] = []; @@ -160,42 +170,15 @@ const TeamsAndUsersPage = () => { } else { setAllTabList(userList, activeUserTab); } + setIsUsersLoading(false); }; const handleAddNewUser = () => { history.push(ROUTES.CREATE_USER); }; - const updateUser = (id: string, data: Operation[], updatedUser: User) => { - setIsLoading(true); - updateUserDetail(id, data) - .then((res) => { - if (res.data) { - const updatedData = (userList || []).map((user) => { - if (user.id === id) { - return updatedUser; - } - - return user; - }); - setAllTabList(updatedData, activeUserTab); - setUserList(updatedData); - } else { - throw jsonData['api-error-messages']['unexpected-server-response']; - } - }) - .catch((err: AxiosError) => { - showErrorToast( - err, - jsonData['api-error-messages']['update-user-error'] - ); - }) - .finally(() => { - setIsLoading(false); - }); - }; const handleDeleteUser = (id: string) => { - setIsLoading(true); + setIsUsersLoading(true); deleteUser(id) .then(() => { AppState.updateUsers((userList || []).filter((item) => item.id !== id)); @@ -208,6 +191,7 @@ const TeamsAndUsersPage = () => { }) .finally(() => { setIsLoading(false); + setIsUsersLoading(false); }); }; @@ -218,21 +202,25 @@ const TeamsAndUsersPage = () => { team: string, pagin = {} as { [key: string]: string } ) => { - getUsers('', PAGE_SIZE_MEDIUM, { team, ...pagin }).then( - (res: AxiosResponse) => { + setIsTeamMemberLoading(true); + getUsers('', PAGE_SIZE_MEDIUM, { team, ...pagin }) + .then((res: AxiosResponse) => { if (res.data) { setCurrentTeamUsers(res.data.data); setTeamUserPagin(res.data.paging); } - } - ); + }) + .catch(() => { + setCurrentTeamUsers([]); + setTeamUserPagin({ total: 0 }); + }) + .finally(() => setIsTeamMemberLoading(false)); }; /** * Make API call to fetch all the teams */ const fetchTeams = () => { - setIsLoading(true); getTeams(['users', 'owns', 'defaultRoles', 'owner']) .then((res: AxiosResponse) => { if (res.data) { @@ -255,6 +243,7 @@ const TeamsAndUsersPage = () => { }) .finally(() => { setIsLoading(false); + handleRightPannelLoading(false); }); }; @@ -263,7 +252,6 @@ const TeamsAndUsersPage = () => { */ const fetchCurrentTeam = (name: string, update = false) => { if (currentTeam?.name !== name || update) { - // setIsLoading(true); getTeamByName(name, ['users', 'owns', 'defaultRoles', 'owner']) .then((res: AxiosResponse) => { if (res.data) { @@ -299,6 +287,7 @@ const TeamsAndUsersPage = () => { }; const searchUsers = (text: string, currentPage: number) => { + setIsTeamMemberLoading(true); searchData( text, currentPage, @@ -317,7 +306,8 @@ const TeamsAndUsersPage = () => { }) .catch(() => { setCurrentTeamUsers([]); - }); + }) + .finally(() => setIsTeamMemberLoading(false)); }; const teamUserPaginHandler = (cursorValue: string | number) => { @@ -418,6 +408,7 @@ const TeamsAndUsersPage = () => { * @param name - team name */ const changeCurrentTeam = (name: string, isUsersCategory: boolean) => { + handleRightPannelLoading(true); history.push(getTeamAndUserDetailsPath(name)); if (isUsersCategory) { setIsTeamVisible(false); @@ -635,7 +626,10 @@ const TeamsAndUsersPage = () => { isAddingTeam={isAddingTeam} isAddingUsers={isAddingUsers} isDescriptionEditable={isDescriptionEditable} + isRightPannelLoading={isRightPannelLoading} + isTeamMemberLoading={isTeamMemberLoading} isTeamVisible={isTeamVisible} + isUsersLoading={isUsersLoading} removeUserFromTeam={removeUserFromTeam} selectedUserList={selectedUserList} teamUserPagin={teamUserPagin} @@ -643,7 +637,6 @@ const TeamsAndUsersPage = () => { teamUsersSearchText={teamUsersSearchText} teams={teams} updateTeamHandler={updateTeamHandler} - updateUser={updateUser} userSearchTerm={userSearchTerm} users={users} onDescriptionUpdate={onDescriptionUpdate} diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/teams/UserCard.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/teams/UserCard.tsx index 08d56110356..99f0135e15a 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/teams/UserCard.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/teams/UserCard.tsx @@ -43,6 +43,7 @@ interface Props { isDataset?: boolean; isCheckBoxes?: boolean; isOwner?: boolean; + onTitleClick?: (value: string) => void; onSelect?: (value: string) => void; onRemove?: (value: string) => void; } @@ -54,6 +55,7 @@ const UserCard = ({ isDataset = false, isCheckBoxes = false, isOwner = false, + onTitleClick, onSelect, onRemove, }: Props) => { @@ -172,9 +174,16 @@ const UserCard = ({

+ title={item.displayName} + onClick={() => { + onTitleClick?.(item.fqn); + }}> {item.displayName}

{item.name && (