mirror of
				https://github.com/open-metadata/OpenMetadata.git
				synced 2025-10-31 10:39:30 +00:00 
			
		
		
		
	* Fix #3683: Allow Admins to delete a user from Users page
This commit is contained in:
		
							parent
							
								
									909cfe5736
								
							
						
					
					
						commit
						40fa4dcdcb
					
				| @ -106,3 +106,7 @@ export const getUserCounts = () => { | |||||||
|     `/search/query?q=*&from=0&size=0&index=${SearchIndex.USER}` |     `/search/query?q=*&from=0&size=0&index=${SearchIndex.USER}` | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|  | 
 | ||||||
|  | export const deleteUser = (id: string) => { | ||||||
|  |   return APIClient.delete(`/users/${id}`); | ||||||
|  | }; | ||||||
|  | |||||||
| @ -26,16 +26,43 @@ const mockItem = { | |||||||
|   teamCount: 'Cloud_Infra', |   teamCount: 'Cloud_Infra', | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const mockRemove = jest.fn(); | const mockSelect = jest.fn(); | ||||||
|  | const mockDelete = jest.fn(); | ||||||
|  | 
 | ||||||
|  | jest.mock('../../auth-provider/AuthProvider', () => { | ||||||
|  |   return { | ||||||
|  |     useAuthContext: jest.fn(() => ({ | ||||||
|  |       isAuthDisabled: false, | ||||||
|  |       isAuthenticated: true, | ||||||
|  |       isProtectedRoute: jest.fn().mockReturnValue(true), | ||||||
|  |       isTourRoute: jest.fn().mockReturnValue(false), | ||||||
|  |       onLogoutHandler: jest.fn(), | ||||||
|  |     })), | ||||||
|  |   }; | ||||||
|  | }); | ||||||
| 
 | 
 | ||||||
| jest.mock('../../components/common/avatar/Avatar', () => { | jest.mock('../../components/common/avatar/Avatar', () => { | ||||||
|   return jest.fn().mockReturnValue(<p data-testid="avatar">Avatar</p>); |   return jest.fn().mockReturnValue(<p data-testid="avatar">Avatar</p>); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
|  | jest.mock('../../utils/SvgUtils', () => { | ||||||
|  |   return { | ||||||
|  |     __esModule: true, | ||||||
|  |     default: jest.fn().mockReturnValue(<p data-testid="svg-icon">SVGIcons</p>), | ||||||
|  |     Icons: { | ||||||
|  |       DELETE: 'delete', | ||||||
|  |     }, | ||||||
|  |   }; | ||||||
|  | }); | ||||||
|  | 
 | ||||||
| describe('Test UserDataCard component', () => { | describe('Test UserDataCard component', () => { | ||||||
|   it('Component should render', async () => { |   it('Component should render', async () => { | ||||||
|     const { container } = render( |     const { container } = render( | ||||||
|       <UserDataCard item={mockItem} onClick={mockRemove} />, |       <UserDataCard | ||||||
|  |         item={mockItem} | ||||||
|  |         onClick={mockSelect} | ||||||
|  |         onDelete={mockDelete} | ||||||
|  |       />, | ||||||
|       { |       { | ||||||
|         wrapper: MemoryRouter, |         wrapper: MemoryRouter, | ||||||
|       } |       } | ||||||
| @ -50,7 +77,11 @@ describe('Test UserDataCard component', () => { | |||||||
| 
 | 
 | ||||||
|   it('Data should render', async () => { |   it('Data should render', async () => { | ||||||
|     const { container } = render( |     const { container } = render( | ||||||
|       <UserDataCard item={mockItem} onClick={mockRemove} />, |       <UserDataCard | ||||||
|  |         item={mockItem} | ||||||
|  |         onClick={mockSelect} | ||||||
|  |         onDelete={mockDelete} | ||||||
|  |       />, | ||||||
|       { |       { | ||||||
|         wrapper: MemoryRouter, |         wrapper: MemoryRouter, | ||||||
|       } |       } | ||||||
|  | |||||||
| @ -12,8 +12,11 @@ | |||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
| import classNames from 'classnames'; | import classNames from 'classnames'; | ||||||
|  | import { isNil } from 'lodash'; | ||||||
| import React from 'react'; | import React from 'react'; | ||||||
|  | import SVGIcons, { Icons } from '../../utils/SvgUtils'; | ||||||
| import Avatar from '../common/avatar/Avatar'; | import Avatar from '../common/avatar/Avatar'; | ||||||
|  | import NonAdminAction from '../common/non-admin-action/NonAdminAction'; | ||||||
| 
 | 
 | ||||||
| type Item = { | type Item = { | ||||||
|   description: string; |   description: string; | ||||||
| @ -28,47 +31,74 @@ type Item = { | |||||||
| type Props = { | type Props = { | ||||||
|   item: Item; |   item: Item; | ||||||
|   onClick: (value: string) => void; |   onClick: (value: string) => void; | ||||||
|  |   onDelete?: (id: string, name: string) => void; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const UserDataCard = ({ item, onClick }: Props) => { | const UserDataCard = ({ item, onClick, onDelete }: Props) => { | ||||||
|   return ( |   return ( | ||||||
|     <div |     <div | ||||||
|       className="tw-card tw-flex tw-gap-1 tw-py-2 tw-px-3 tw-group" |       className="tw-card tw-flex tw-justify-between tw-py-2 tw-px-3 tw-group" | ||||||
|       data-testid="user-card-container"> |       data-testid="user-card-container"> | ||||||
|       {item.profilePhoto ? ( |       <div className="tw-flex tw-gap-1"> | ||||||
|         <div className="tw-h-9 tw-w-9"> |         {item.profilePhoto ? ( | ||||||
|           <img |           <div className="tw-h-9 tw-w-9"> | ||||||
|             alt="profile" |             <img | ||||||
|             className="tw-rounded-full tw-w-full" |               alt="profile" | ||||||
|             src={item.profilePhoto} |               className="tw-rounded-full tw-w-full" | ||||||
|           /> |               src={item.profilePhoto} | ||||||
|         </div> |             /> | ||||||
|       ) : ( |           </div> | ||||||
|         <Avatar name={item.description} /> |         ) : ( | ||||||
|       )} |           <Avatar name={item.description} /> | ||||||
|  |         )} | ||||||
| 
 | 
 | ||||||
|       <div |         <div | ||||||
|         className="tw-flex tw-flex-col tw-flex-1 tw-pl-2" |           className="tw-flex tw-flex-col tw-flex-1 tw-pl-2" | ||||||
|         data-testid="data-container"> |           data-testid="data-container"> | ||||||
|         <div className="tw-flex tw-justify-between"> |           <div className="tw-flex tw-justify-between"> | ||||||
|           <p |             <p | ||||||
|             className={classNames('tw-font-normal', { |               className={classNames('tw-font-normal', { | ||||||
|               'tw-cursor-pointer': Boolean(onClick), |                 'tw-cursor-pointer': Boolean(onClick), | ||||||
|             })} |               })} | ||||||
|             onClick={() => { |               onClick={() => { | ||||||
|               onClick(item.id as string); |                 onClick(item.id as string); | ||||||
|             }}> |               }}> | ||||||
|             {item.description} |               {item.description} | ||||||
|           </p> |             </p> | ||||||
|           {!item.isActiveUser && ( |             {!item.isActiveUser && ( | ||||||
|             <span className="tw-text-xs tw-bg-badge tw-border tw-px-2 tw-py-0.5 tw-rounded"> |               <span className="tw-text-xs tw-bg-badge tw-border tw-px-2 tw-py-0.5 tw-rounded"> | ||||||
|               Inactive |                 Inactive | ||||||
|             </span> |               </span> | ||||||
|           )} |             )} | ||||||
|  |           </div> | ||||||
|  |           <p className="tw-truncate">{item.email}</p> | ||||||
|  |           <p>Teams: {item.teamCount}</p> | ||||||
|         </div> |         </div> | ||||||
|         <p className="tw-truncate">{item.email}</p> |  | ||||||
|         <p>Teams: {item.teamCount}</p> |  | ||||||
|       </div> |       </div> | ||||||
|  |       {!isNil(onDelete) && ( | ||||||
|  |         <div className="tw-flex-none"> | ||||||
|  |           <NonAdminAction | ||||||
|  |             position="bottom" | ||||||
|  |             title="You do not have permission to delete user."> | ||||||
|  |             <span | ||||||
|  |               className="tw-h-8 tw-rounded tw-mb-3" | ||||||
|  |               data-testid="remove" | ||||||
|  |               onClick={(e) => { | ||||||
|  |                 e.preventDefault(); | ||||||
|  |                 e.stopPropagation(); | ||||||
|  |                 onDelete(item.id as string, item.description); | ||||||
|  |               }}> | ||||||
|  |               <SVGIcons | ||||||
|  |                 alt="delete" | ||||||
|  |                 className="tw-cursor-pointer tw-opacity-0 group-hover:tw-opacity-100" | ||||||
|  |                 icon={Icons.DELETE} | ||||||
|  |                 title="Delete" | ||||||
|  |                 width="12px" | ||||||
|  |               /> | ||||||
|  |             </span> | ||||||
|  |           </NonAdminAction> | ||||||
|  |         </div> | ||||||
|  |       )} | ||||||
|     </div> |     </div> | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|  | |||||||
| @ -27,6 +27,7 @@ import { Button } from '../buttons/Button/Button'; | |||||||
| import ErrorPlaceHolder from '../common/error-with-placeholder/ErrorPlaceHolder'; | import ErrorPlaceHolder from '../common/error-with-placeholder/ErrorPlaceHolder'; | ||||||
| import NonAdminAction from '../common/non-admin-action/NonAdminAction'; | import NonAdminAction from '../common/non-admin-action/NonAdminAction'; | ||||||
| import Searchbar from '../common/searchbar/Searchbar'; | import Searchbar from '../common/searchbar/Searchbar'; | ||||||
|  | import ConfirmationModal from '../Modals/ConfirmationModal/ConfirmationModal'; | ||||||
| import UserDetailsModal from '../Modals/UserDetailsModal/UserDetailsModal'; | import UserDetailsModal from '../Modals/UserDetailsModal/UserDetailsModal'; | ||||||
| import UserDataCard from '../UserDataCard/UserDataCard'; | import UserDataCard from '../UserDataCard/UserDataCard'; | ||||||
| 
 | 
 | ||||||
| @ -34,14 +35,21 @@ interface Props { | |||||||
|   teams: Array<Team>; |   teams: Array<Team>; | ||||||
|   roles: Array<Role>; |   roles: Array<Role>; | ||||||
|   allUsers: Array<User>; |   allUsers: Array<User>; | ||||||
|  |   deleteUser: (id: string) => void; | ||||||
|   updateUser: (id: string, data: Operation[], updatedUser: User) => void; |   updateUser: (id: string, data: Operation[], updatedUser: User) => void; | ||||||
|   handleAddUserClick: () => void; |   handleAddUserClick: () => void; | ||||||
|   isLoading: boolean; |   isLoading: boolean; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | interface DeleteUserInfo { | ||||||
|  |   name: string; | ||||||
|  |   id: string; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| const UserList: FunctionComponent<Props> = ({ | const UserList: FunctionComponent<Props> = ({ | ||||||
|   allUsers = [], |   allUsers = [], | ||||||
|   isLoading, |   isLoading, | ||||||
|  |   deleteUser, | ||||||
|   updateUser, |   updateUser, | ||||||
|   handleAddUserClick, |   handleAddUserClick, | ||||||
|   teams = [], |   teams = [], | ||||||
| @ -55,6 +63,7 @@ const UserList: FunctionComponent<Props> = ({ | |||||||
|   const [currentTab, setCurrentTab] = useState<number>(1); |   const [currentTab, setCurrentTab] = useState<number>(1); | ||||||
|   const [selectedUser, setSelectedUser] = useState<User>(); |   const [selectedUser, setSelectedUser] = useState<User>(); | ||||||
|   const [searchText, setSearchText] = useState(''); |   const [searchText, setSearchText] = useState(''); | ||||||
|  |   const [deletingUser, setDeletingUser] = useState<DeleteUserInfo>(); | ||||||
| 
 | 
 | ||||||
|   const handleSearchAction = (searchValue: string) => { |   const handleSearchAction = (searchValue: string) => { | ||||||
|     setSearchText(searchValue); |     setSearchText(searchValue); | ||||||
| @ -160,6 +169,18 @@ const UserList: FunctionComponent<Props> = ({ | |||||||
|     } |     } | ||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|  |   const handleDeleteUser = (id: string, name: string) => { | ||||||
|  |     setDeletingUser({ | ||||||
|  |       name, | ||||||
|  |       id, | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   const onConfirmDeleteUser = (id: string) => { | ||||||
|  |     deleteUser(id); | ||||||
|  |     setDeletingUser(undefined); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|   const handleTabChange = (tab: number) => { |   const handleTabChange = (tab: number) => { | ||||||
|     setSearchText(''); |     setSearchText(''); | ||||||
|     setCurrentTab(tab); |     setCurrentTab(tab); | ||||||
| @ -343,7 +364,11 @@ const UserList: FunctionComponent<Props> = ({ | |||||||
|                 className="tw-cursor-pointer" |                 className="tw-cursor-pointer" | ||||||
|                 key={index} |                 key={index} | ||||||
|                 onClick={() => selectUser(User.id)}> |                 onClick={() => selectUser(User.id)}> | ||||||
|                 <UserDataCard item={User} onClick={selectUser} /> |                 <UserDataCard | ||||||
|  |                   item={User} | ||||||
|  |                   onClick={selectUser} | ||||||
|  |                   onDelete={handleDeleteUser} | ||||||
|  |                 /> | ||||||
|               </div> |               </div> | ||||||
|             ); |             ); | ||||||
|           })} |           })} | ||||||
| @ -376,6 +401,18 @@ const UserList: FunctionComponent<Props> = ({ | |||||||
|                     onSave={handleSave} |                     onSave={handleSave} | ||||||
|                   /> |                   /> | ||||||
|                 )} |                 )} | ||||||
|  |                 {!isUndefined(deletingUser) && ( | ||||||
|  |                   <ConfirmationModal | ||||||
|  |                     bodyText={`Are you sure you want to delete ${deletingUser.name}?`} | ||||||
|  |                     cancelText="Cancel" | ||||||
|  |                     confirmText="Confirm" | ||||||
|  |                     header="Delete user" | ||||||
|  |                     onCancel={() => setDeletingUser(undefined)} | ||||||
|  |                     onConfirm={() => { | ||||||
|  |                       onConfirmDeleteUser(deletingUser.id); | ||||||
|  |                     }} | ||||||
|  |                   /> | ||||||
|  |                 )} | ||||||
|               </> |               </> | ||||||
|             )} |             )} | ||||||
|           </> |           </> | ||||||
|  | |||||||
| @ -27,11 +27,12 @@ const jsonData = { | |||||||
| 
 | 
 | ||||||
|     'delete-glossary-error': 'Error while deleting glossary!', |     'delete-glossary-error': 'Error while deleting glossary!', | ||||||
|     'delete-glossary-term-error': 'Error while deleting glossary term!', |     'delete-glossary-term-error': 'Error while deleting glossary term!', | ||||||
|     'delete-team-error': 'Error while deleting team!', |  | ||||||
|     'delete-lineage-error': 'Error while deleting edge!', |  | ||||||
|     'delete-test-error': 'Error while deleting test!', |  | ||||||
|     'delete-message-error': 'Error while deleting message!', |  | ||||||
|     'delete-ingestion-error': 'Error while deleting ingestion workflow', |     'delete-ingestion-error': 'Error while deleting ingestion workflow', | ||||||
|  |     'delete-lineage-error': 'Error while deleting edge!', | ||||||
|  |     'delete-message-error': 'Error while deleting message!', | ||||||
|  |     'delete-team-error': 'Error while deleting team!', | ||||||
|  |     'delete-test-error': 'Error while deleting test!', | ||||||
|  |     'delete-user-error': 'Error while deleting user!', | ||||||
| 
 | 
 | ||||||
|     'elastic-search-error': 'Error while fetch data from Elasticsearch!', |     'elastic-search-error': 'Error while fetch data from Elasticsearch!', | ||||||
| 
 | 
 | ||||||
| @ -46,6 +47,7 @@ const jsonData = { | |||||||
|     'fetch-glossary-error': 'Error while fetching glossary!', |     'fetch-glossary-error': 'Error while fetching glossary!', | ||||||
|     'fetch-glossary-list-error': 'Error while fetching glossaries!', |     'fetch-glossary-list-error': 'Error while fetching glossaries!', | ||||||
|     'fetch-glossary-term-error': 'Error while fetching glossary term!', |     'fetch-glossary-term-error': 'Error while fetching glossary term!', | ||||||
|  |     'fetch-ingestion-error': 'Error while fetching ingestion workflow!', | ||||||
|     'fetch-lineage-error': 'Error while fetching lineage data!', |     'fetch-lineage-error': 'Error while fetching lineage data!', | ||||||
|     'fetch-lineage-node-error': 'Error while fetching lineage node!', |     'fetch-lineage-node-error': 'Error while fetching lineage node!', | ||||||
|     'fetch-pipeline-details-error': 'Error while fetching pipeline details!', |     'fetch-pipeline-details-error': 'Error while fetching pipeline details!', | ||||||
| @ -57,23 +59,24 @@ const jsonData = { | |||||||
|     'fetch-thread-error': 'Error while fetching threads!', |     'fetch-thread-error': 'Error while fetching threads!', | ||||||
|     'fetch-updated-conversation-error': |     'fetch-updated-conversation-error': | ||||||
|       'Error while fetching updated conversation!', |       'Error while fetching updated conversation!', | ||||||
|     'fetch-ingestion-error': 'Error while fetching ingestion workflow!', |  | ||||||
|     'fetch-service-error': 'Error while fetching service details!', |     'fetch-service-error': 'Error while fetching service details!', | ||||||
|  |     'fetch-teams-error': 'Error while fetching teams!', | ||||||
| 
 | 
 | ||||||
|     'unexpected-server-response': 'Unexpected response from server!', |     'unexpected-server-response': 'Unexpected response from server!', | ||||||
| 
 | 
 | ||||||
|     'update-chart-error': 'Error while updating charts!', |     'update-chart-error': 'Error while updating charts!', | ||||||
|     'update-owner-error': 'Error while updating owner', |  | ||||||
|     'update-glossary-term-error': 'Error while updating glossary term!', |  | ||||||
|     'update-description-error': 'Error while updating description!', |     'update-description-error': 'Error while updating description!', | ||||||
|     'update-entity-error': 'Error while updating entity!', |     'update-entity-error': 'Error while updating entity!', | ||||||
|     'update-team-error': 'Error while updating team', |  | ||||||
|     'update-tags-error': 'Error while updating tags!', |  | ||||||
|     'update-task-error': 'Error while updating tasks!', |  | ||||||
|     'update-entity-follow-error': 'Error while following entity!', |     'update-entity-follow-error': 'Error while following entity!', | ||||||
|     'update-entity-unfollow-error': 'Error while unfollowing entity!', |     'update-entity-unfollow-error': 'Error while unfollowing entity!', | ||||||
|  |     'update-glossary-term-error': 'Error while updating glossary term!', | ||||||
|     'update-ingestion-error': 'Error while updating ingestion workflow', |     'update-ingestion-error': 'Error while updating ingestion workflow', | ||||||
|  |     'update-owner-error': 'Error while updating owner', | ||||||
|     'update-service-config-error': 'Error while updating ingestion workflow', |     'update-service-config-error': 'Error while updating ingestion workflow', | ||||||
|  |     'update-tags-error': 'Error while updating tags!', | ||||||
|  |     'update-task-error': 'Error while updating tasks!', | ||||||
|  |     'update-team-error': 'Error while updating team!', | ||||||
|  |     'update-user-error': 'Error while updating user!', | ||||||
|   }, |   }, | ||||||
|   'api-success-messages': { |   'api-success-messages': { | ||||||
|     'create-conversation': 'Conversation created successfully!', |     'create-conversation': 'Conversation created successfully!', | ||||||
|  | |||||||
| @ -1,6 +1,13 @@ | |||||||
| import { findByTestId, findByText, render } from '@testing-library/react'; | import { | ||||||
|  |   act, | ||||||
|  |   findByTestId, | ||||||
|  |   findByText, | ||||||
|  |   fireEvent, | ||||||
|  |   render, | ||||||
|  | } from '@testing-library/react'; | ||||||
| import React, { ReactNode } from 'react'; | import React, { ReactNode } from 'react'; | ||||||
| import { MemoryRouter } from 'react-router-dom'; | import { MemoryRouter } from 'react-router-dom'; | ||||||
|  | import { deleteUser } from '../../axiosAPIs/userAPI'; | ||||||
| import UserListPage from './UserListPage'; | import UserListPage from './UserListPage'; | ||||||
| 
 | 
 | ||||||
| jest.mock('../../components/containers/PageContainerV1', () => { | jest.mock('../../components/containers/PageContainerV1', () => { | ||||||
| @ -12,13 +19,23 @@ jest.mock('../../components/containers/PageContainerV1', () => { | |||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| jest.mock('../../components/UserList/UserList', () => { | jest.mock('../../components/UserList/UserList', () => { | ||||||
|   return jest.fn().mockImplementation(() => <div>UserListComponent</div>); |   return jest | ||||||
|  |     .fn() | ||||||
|  |     .mockImplementation(({ deleteUser }) => ( | ||||||
|  |       <div onClick={deleteUser}>UserListComponent</div> | ||||||
|  |     )); | ||||||
| }); | }); | ||||||
| 
 | 
 | ||||||
| jest.mock('../../axiosAPIs/teamsAPI', () => ({ | jest.mock('../../axiosAPIs/teamsAPI', () => ({ | ||||||
|   getTeams: jest.fn().mockImplementation(() => Promise.resolve()), |   getTeams: jest.fn().mockImplementation(() => Promise.resolve()), | ||||||
| })); | })); | ||||||
| 
 | 
 | ||||||
|  | jest.mock('../../axiosAPIs/userAPI', () => ({ | ||||||
|  |   deleteUser: jest.fn().mockImplementation(() => Promise.resolve()), | ||||||
|  | })); | ||||||
|  | 
 | ||||||
|  | const mockDeleteUser = jest.fn(() => Promise.resolve({})); | ||||||
|  | 
 | ||||||
| describe('Test UserListPage component', () => { | describe('Test UserListPage component', () => { | ||||||
|   it('UserListPage component should render properly', async () => { |   it('UserListPage component should render properly', async () => { | ||||||
|     const { container } = render(<UserListPage />, { |     const { container } = render(<UserListPage />, { | ||||||
| @ -31,4 +48,18 @@ describe('Test UserListPage component', () => { | |||||||
|     expect(PageContainerV1).toBeInTheDocument(); |     expect(PageContainerV1).toBeInTheDocument(); | ||||||
|     expect(UserListComponent).toBeInTheDocument(); |     expect(UserListComponent).toBeInTheDocument(); | ||||||
|   }); |   }); | ||||||
|  | 
 | ||||||
|  |   it('should delete users', async () => { | ||||||
|  |     (deleteUser as jest.Mock).mockImplementationOnce(mockDeleteUser); | ||||||
|  | 
 | ||||||
|  |     const { container } = render(<UserListPage />, { | ||||||
|  |       wrapper: MemoryRouter, | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     await act(async () => { | ||||||
|  |       fireEvent.click(await findByText(container, 'UserListComponent')); | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     expect(mockDeleteUser).toBeCalled(); | ||||||
|  |   }); | ||||||
| }); | }); | ||||||
|  | |||||||
| @ -13,12 +13,13 @@ | |||||||
| 
 | 
 | ||||||
| import { AxiosError, AxiosResponse } from 'axios'; | import { AxiosError, AxiosResponse } from 'axios'; | ||||||
| import { Operation } from 'fast-json-patch'; | import { Operation } from 'fast-json-patch'; | ||||||
|  | import { isNil } from 'lodash'; | ||||||
| import { observer } from 'mobx-react'; | import { observer } from 'mobx-react'; | ||||||
| import React, { useEffect, useState } from 'react'; | import React, { useEffect, useState } from 'react'; | ||||||
| import { useHistory } from 'react-router-dom'; | import { useHistory } from 'react-router-dom'; | ||||||
| import AppState from '../../AppState'; | import AppState from '../../AppState'; | ||||||
| import { getTeams } from '../../axiosAPIs/teamsAPI'; | import { getTeams } from '../../axiosAPIs/teamsAPI'; | ||||||
| import { updateUserDetail } from '../../axiosAPIs/userAPI'; | import { deleteUser, updateUserDetail } from '../../axiosAPIs/userAPI'; | ||||||
| import PageContainerV1 from '../../components/containers/PageContainerV1'; | import PageContainerV1 from '../../components/containers/PageContainerV1'; | ||||||
| import UserList from '../../components/UserList/UserList'; | import UserList from '../../components/UserList/UserList'; | ||||||
| import { ROUTES } from '../../constants/constants'; | import { ROUTES } from '../../constants/constants'; | ||||||
| @ -26,6 +27,8 @@ import { Role } from '../../generated/entity/teams/role'; | |||||||
| import { Team } from '../../generated/entity/teams/team'; | import { Team } from '../../generated/entity/teams/team'; | ||||||
| import { User } from '../../generated/entity/teams/user'; | import { User } from '../../generated/entity/teams/user'; | ||||||
| import useToastContext from '../../hooks/useToastContext'; | import useToastContext from '../../hooks/useToastContext'; | ||||||
|  | import jsonData from '../../jsons/en'; | ||||||
|  | import { getErrorText } from '../../utils/StringsUtils'; | ||||||
| 
 | 
 | ||||||
| const UserListPage = () => { | const UserListPage = () => { | ||||||
|   const showToast = useToastContext(); |   const showToast = useToastContext(); | ||||||
| @ -33,20 +36,30 @@ const UserListPage = () => { | |||||||
| 
 | 
 | ||||||
|   const [teams, setTeams] = useState<Array<Team>>([]); |   const [teams, setTeams] = useState<Array<Team>>([]); | ||||||
|   const [roles, setRoles] = useState<Array<Role>>([]); |   const [roles, setRoles] = useState<Array<Role>>([]); | ||||||
|   const [isLoading, setIsLoading] = useState<boolean>(false); |   const [isLoading, setIsLoading] = useState<boolean>(true); | ||||||
|   const [allUsers, setAllUsers] = useState<Array<User>>([]); |   const [allUsers, setAllUsers] = useState<Array<User>>(); | ||||||
|  | 
 | ||||||
|  |   const handleShowErrorToast = (message: string) => { | ||||||
|  |     showToast({ | ||||||
|  |       variant: 'error', | ||||||
|  |       body: message, | ||||||
|  |     }); | ||||||
|  |   }; | ||||||
| 
 | 
 | ||||||
|   const fetchTeams = () => { |   const fetchTeams = () => { | ||||||
|     setIsLoading(true); |     setIsLoading(true); | ||||||
|     getTeams(['users']) |     getTeams(['users']) | ||||||
|       .then((res: AxiosResponse) => { |       .then((res: AxiosResponse) => { | ||||||
|         setTeams(res.data.data); |         if (res.data) { | ||||||
|  |           setTeams(res.data.data); | ||||||
|  |         } else { | ||||||
|  |           throw jsonData['api-error-messages']['unexpected-server-response']; | ||||||
|  |         } | ||||||
|       }) |       }) | ||||||
|       .catch((err: AxiosError) => { |       .catch((err: AxiosError) => { | ||||||
|         showToast({ |         handleShowErrorToast( | ||||||
|           variant: 'error', |           getErrorText(err, jsonData['api-error-messages']['fetch-teams-error']) | ||||||
|           body: err.message || 'No teams available!', |         ); | ||||||
|         }); |  | ||||||
|       }) |       }) | ||||||
|       .finally(() => { |       .finally(() => { | ||||||
|         setIsLoading(false); |         setIsLoading(false); | ||||||
| @ -63,15 +76,41 @@ const UserListPage = () => { | |||||||
|   const updateUser = (id: string, data: Operation[], updatedUser: User) => { |   const updateUser = (id: string, data: Operation[], updatedUser: User) => { | ||||||
|     setIsLoading(true); |     setIsLoading(true); | ||||||
|     updateUserDetail(id, data) |     updateUserDetail(id, data) | ||||||
|       .then(() => { |       .then((res) => { | ||||||
|         setAllUsers( |         if (res.data) { | ||||||
|           allUsers.map((user) => { |           setAllUsers( | ||||||
|             if (user.id === id) { |             (allUsers || []).map((user) => { | ||||||
|               return updatedUser; |               if (user.id === id) { | ||||||
|             } |                 return updatedUser; | ||||||
|  |               } | ||||||
| 
 | 
 | ||||||
|             return user; |               return user; | ||||||
|           }) |             }) | ||||||
|  |           ); | ||||||
|  |         } else { | ||||||
|  |           throw jsonData['api-error-messages']['unexpected-server-response']; | ||||||
|  |         } | ||||||
|  |       }) | ||||||
|  |       .catch((err: AxiosError) => { | ||||||
|  |         handleShowErrorToast( | ||||||
|  |           getErrorText(err, jsonData['api-error-messages']['update-user-error']) | ||||||
|  |         ); | ||||||
|  |       }) | ||||||
|  |       .finally(() => { | ||||||
|  |         setIsLoading(false); | ||||||
|  |       }); | ||||||
|  |   }; | ||||||
|  | 
 | ||||||
|  |   const handleDeleteUser = (id: string) => { | ||||||
|  |     setIsLoading(true); | ||||||
|  |     deleteUser(id) | ||||||
|  |       .then(() => { | ||||||
|  |         AppState.updateUsers((allUsers || []).filter((item) => item.id !== id)); | ||||||
|  |         fetchTeams(); | ||||||
|  |       }) | ||||||
|  |       .catch((err: AxiosError) => { | ||||||
|  |         handleShowErrorToast( | ||||||
|  |           getErrorText(err, jsonData['api-error-messages']['delete-user-error']) | ||||||
|         ); |         ); | ||||||
|       }) |       }) | ||||||
|       .finally(() => { |       .finally(() => { | ||||||
| @ -80,7 +119,11 @@ const UserListPage = () => { | |||||||
|   }; |   }; | ||||||
| 
 | 
 | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     setAllUsers(AppState.users); |     if (AppState.users.length) { | ||||||
|  |       setAllUsers(AppState.users); | ||||||
|  |     } else { | ||||||
|  |       setAllUsers(undefined); | ||||||
|  |     } | ||||||
|   }, [AppState.users]); |   }, [AppState.users]); | ||||||
|   useEffect(() => { |   useEffect(() => { | ||||||
|     setRoles(AppState.userRoles); |     setRoles(AppState.userRoles); | ||||||
| @ -93,9 +136,10 @@ const UserListPage = () => { | |||||||
|   return ( |   return ( | ||||||
|     <PageContainerV1> |     <PageContainerV1> | ||||||
|       <UserList |       <UserList | ||||||
|         allUsers={allUsers} |         allUsers={allUsers || []} | ||||||
|  |         deleteUser={handleDeleteUser} | ||||||
|         handleAddUserClick={handleAddUserClick} |         handleAddUserClick={handleAddUserClick} | ||||||
|         isLoading={isLoading} |         isLoading={isLoading || isNil(allUsers)} | ||||||
|         roles={roles} |         roles={roles} | ||||||
|         teams={teams} |         teams={teams} | ||||||
|         updateUser={updateUser} |         updateUser={updateUser} | ||||||
|  | |||||||
| @ -797,7 +797,7 @@ const TeamsPage = () => { | |||||||
|                 )} |                 )} | ||||||
|                 {deletingUser.state && ( |                 {deletingUser.state && ( | ||||||
|                   <ConfirmationModal |                   <ConfirmationModal | ||||||
|                     bodyText={`Are you sure want to remove ${ |                     bodyText={`Are you sure you want to remove ${ | ||||||
|                       deletingUser.user?.displayName ?? deletingUser.user?.name |                       deletingUser.user?.displayName ?? deletingUser.user?.name | ||||||
|                     }?`}
 |                     }?`}
 | ||||||
|                     cancelText="Cancel" |                     cancelText="Cancel" | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 darth-coder00
						darth-coder00