mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-11-13 17:32:53 +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