mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-10-17 03:38:18 +00:00
UI: Added users and admin page to global settings menu (#6429)
* added users and admin page to global settings menu * addressing comment
This commit is contained in:
parent
512b241069
commit
6fb888437a
@ -0,0 +1,10 @@
|
||||
<svg width="14" height="14" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_12104_20402)">
|
||||
<path d="M11.8121 2.38207C10.5267 1.09691 8.81773 0.388984 6.99992 0.388984C6.00789 0.388984 5.03719 0.603359 4.14824 1.01379L4.28934 0.653125C4.38559 0.406757 4.26391 0.129218 4.01781 0.0329683C3.77145 -0.0632817 3.49391 0.058398 3.39766 0.304492L2.8073 1.81469C2.74141 1.98312 2.74852 2.17316 2.82672 2.33641C2.90492 2.49937 3.04875 2.62406 3.22129 2.6782L4.7684 3.16328C4.81598 3.17832 4.86438 3.18543 4.91168 3.18543C5.11539 3.18543 5.30406 3.05445 5.36832 2.84992C5.44734 2.59754 5.30707 2.32902 5.05469 2.25L4.29098 2.01047C5.12305 1.57488 6.04973 1.34684 6.99965 1.34684C8.5618 1.34684 10.0302 1.95523 11.1348 3.05965C12.2393 4.16434 12.8477 5.63269 12.8477 7.19484C12.8477 8.75699 12.2393 10.2256 11.1348 11.33C10.0302 12.4345 8.5618 13.0429 6.99965 13.0429C5.4375 13.0429 3.96887 12.4345 2.86445 11.33C1.75977 10.2254 1.15164 8.75699 1.15164 7.19484C1.15164 6.2657 1.36301 5.37758 1.77973 4.55508C1.89922 4.31937 1.80488 4.03117 1.56918 3.91168C1.33348 3.79219 1.04527 3.88652 0.925781 4.12223C0.44043 5.0798 0.194336 6.1134 0.194336 7.1943C0.194336 9.01211 0.902266 10.7211 2.1877 12.0065C3.47312 13.292 5.18211 13.9999 6.99992 13.9999C8.81773 13.9999 10.5267 13.292 11.8121 12.0065C13.0976 10.7211 13.8055 9.01211 13.8055 7.1943C13.8055 5.37648 13.0976 3.6675 11.8121 2.38207Z" fill="#37352F"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_12104_20402">
|
||||
<rect width="14" height="14" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 1.5 KiB |
@ -121,7 +121,7 @@ export const createUser = (
|
||||
return APIClient.post(`/users`, userDetails);
|
||||
};
|
||||
|
||||
export const updateUser = (data: User): Promise<AxiosResponse> => {
|
||||
export const updateUser = (data: User | CreateUser): Promise<AxiosResponse> => {
|
||||
return APIClient.put('/users', data);
|
||||
};
|
||||
|
||||
|
@ -0,0 +1,265 @@
|
||||
/*
|
||||
* Copyright 2022 Collate
|
||||
* Licensed 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 { Button, Col, Modal, Row, Space, Switch, Table, Tooltip } from 'antd';
|
||||
import { ColumnsType } from 'antd/lib/table';
|
||||
import { AxiosError } from 'axios';
|
||||
import { isUndefined } from 'lodash';
|
||||
import React, { FC, useMemo, useState } from 'react';
|
||||
import { Link, useHistory } from 'react-router-dom';
|
||||
import { updateUser } from '../../axiosAPIs/userAPI';
|
||||
import { getUserPath, PAGE_SIZE, ROUTES } from '../../constants/constants';
|
||||
import { CreateUser } from '../../generated/api/teams/createUser';
|
||||
import { EntityReference, User } from '../../generated/entity/teams/user';
|
||||
import { Paging } from '../../generated/type/paging';
|
||||
import jsonData from '../../jsons/en';
|
||||
import { getEntityName, getTeamsText } from '../../utils/CommonUtils';
|
||||
import SVGIcons, { Icons } from '../../utils/SvgUtils';
|
||||
import { showErrorToast, showSuccessToast } from '../../utils/ToastUtils';
|
||||
import DeleteWidgetModal from '../common/DeleteWidget/DeleteWidgetModal';
|
||||
import NextPrevious from '../common/next-previous/NextPrevious';
|
||||
import Searchbar from '../common/searchbar/Searchbar';
|
||||
import Loader from '../Loader/Loader';
|
||||
import './usersList.less';
|
||||
|
||||
interface UserListV1Props {
|
||||
data: User[];
|
||||
paging: Paging;
|
||||
searchTerm: string;
|
||||
currentPage: number;
|
||||
isDataLoading: boolean;
|
||||
showDeletedUser: boolean;
|
||||
onPagingChange: (cursorValue: string | number, activePage?: number) => void;
|
||||
onShowDeletedUserChange: (value: boolean) => void;
|
||||
onSearch: (text: string) => void;
|
||||
afterDeleteAction: () => void;
|
||||
}
|
||||
|
||||
const UserListV1: FC<UserListV1Props> = ({
|
||||
data,
|
||||
paging,
|
||||
searchTerm,
|
||||
currentPage,
|
||||
isDataLoading,
|
||||
showDeletedUser,
|
||||
onSearch,
|
||||
onShowDeletedUserChange,
|
||||
onPagingChange,
|
||||
afterDeleteAction,
|
||||
}) => {
|
||||
const history = useHistory();
|
||||
const [selectedUser, setSelectedUser] = useState<User>();
|
||||
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||
const [showReactiveModal, setShowReactiveModal] = useState(false);
|
||||
const showRestore = showDeletedUser && !isDataLoading;
|
||||
|
||||
const handleAddNewUser = () => {
|
||||
history.push(ROUTES.CREATE_USER);
|
||||
};
|
||||
|
||||
const handleReactiveUser = async () => {
|
||||
if (isUndefined(selectedUser)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedUserData: CreateUser = {
|
||||
description: selectedUser.description,
|
||||
displayName: selectedUser.displayName,
|
||||
email: selectedUser.email,
|
||||
isAdmin: selectedUser.isAdmin,
|
||||
name: selectedUser.name,
|
||||
profile: selectedUser.profile,
|
||||
roles: selectedUser.roles?.map((role) => role.id),
|
||||
teams: selectedUser.teams?.map((team) => team.id),
|
||||
};
|
||||
|
||||
try {
|
||||
const { data } = await updateUser(updatedUserData);
|
||||
if (data) {
|
||||
afterDeleteAction();
|
||||
showSuccessToast(
|
||||
jsonData['api-success-messages']['user-restored-success']
|
||||
);
|
||||
} else {
|
||||
throw jsonData['api-error-messages']['update-user-error'];
|
||||
}
|
||||
} catch (error) {
|
||||
showErrorToast(
|
||||
error as AxiosError,
|
||||
jsonData['api-error-messages']['update-user-error']
|
||||
);
|
||||
}
|
||||
setSelectedUser(undefined);
|
||||
setShowReactiveModal(false);
|
||||
};
|
||||
|
||||
const columns: ColumnsType<User> = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
title: 'Username',
|
||||
dataIndex: 'username',
|
||||
key: 'username',
|
||||
render: (_, record) => (
|
||||
<Link
|
||||
className="hover:tw-underline tw-cursor-pointer"
|
||||
to={getUserPath(record.fullyQualifiedName || record.name)}>
|
||||
{getEntityName(record)}
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: 'Email',
|
||||
dataIndex: 'email',
|
||||
key: 'email',
|
||||
},
|
||||
{
|
||||
title: 'Teams',
|
||||
dataIndex: 'teams',
|
||||
key: 'teams',
|
||||
render: (teams: EntityReference[]) => getTeamsText(teams),
|
||||
},
|
||||
{
|
||||
title: 'Actions',
|
||||
dataIndex: 'actions',
|
||||
key: 'actions',
|
||||
width: 90,
|
||||
render: (_, record) => (
|
||||
<Space
|
||||
align="center"
|
||||
className="tw-w-full tw-justify-center"
|
||||
size={8}>
|
||||
{showRestore && (
|
||||
<Tooltip placement="bottom" title="Restore">
|
||||
<Button
|
||||
icon={
|
||||
<SVGIcons
|
||||
alt="Restore"
|
||||
className="tw-w-4"
|
||||
icon={Icons.RESTORE}
|
||||
/>
|
||||
}
|
||||
type="text"
|
||||
onClick={() => {
|
||||
setSelectedUser(record);
|
||||
setShowReactiveModal(true);
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip placement="bottom" title="Delete">
|
||||
<Button
|
||||
icon={
|
||||
<SVGIcons
|
||||
alt="Delete"
|
||||
className="tw-w-4"
|
||||
icon={Icons.DELETE}
|
||||
/>
|
||||
}
|
||||
type="text"
|
||||
onClick={() => {
|
||||
setSelectedUser(record);
|
||||
setShowDeleteModal(true);
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
];
|
||||
}, [showRestore]);
|
||||
|
||||
return (
|
||||
<Row className="user-listing" gutter={[16, 16]}>
|
||||
<Col span={8}>
|
||||
<Searchbar
|
||||
removeMargin
|
||||
placeholder="Search for user..."
|
||||
searchValue={searchTerm}
|
||||
typingInterval={500}
|
||||
onSearch={onSearch}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={16}>
|
||||
<Space align="center" className="tw-w-full tw-justify-end" size={16}>
|
||||
<span>
|
||||
<Switch
|
||||
checked={showDeletedUser}
|
||||
onClick={onShowDeletedUserChange}
|
||||
/>
|
||||
<span className="tw-ml-2">Deleted Users</span>
|
||||
</span>
|
||||
<Button type="primary" onClick={handleAddNewUser}>
|
||||
Add User
|
||||
</Button>
|
||||
</Space>
|
||||
</Col>
|
||||
|
||||
<Col span={24}>
|
||||
<Table
|
||||
className="user-list-table"
|
||||
columns={columns}
|
||||
dataSource={data}
|
||||
loading={{
|
||||
spinning: isDataLoading,
|
||||
indicator: <Loader size="small" />,
|
||||
}}
|
||||
pagination={false}
|
||||
size="middle"
|
||||
/>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
{paging.total > PAGE_SIZE && (
|
||||
<NextPrevious
|
||||
currentPage={currentPage}
|
||||
isNumberBased={Boolean(searchTerm)}
|
||||
pageSize={PAGE_SIZE}
|
||||
paging={paging}
|
||||
pagingHandler={onPagingChange}
|
||||
totalCount={paging.total}
|
||||
/>
|
||||
)}
|
||||
</Col>
|
||||
|
||||
<Modal
|
||||
cancelButtonProps={{
|
||||
type: 'link',
|
||||
}}
|
||||
className="reactive-modal"
|
||||
okText="Restore"
|
||||
title="restore User"
|
||||
visible={showReactiveModal}
|
||||
onCancel={() => {
|
||||
setShowReactiveModal(false);
|
||||
setSelectedUser(undefined);
|
||||
}}
|
||||
onOk={handleReactiveUser}>
|
||||
<p>Are you sure you want to restore {getEntityName(selectedUser)}?</p>
|
||||
</Modal>
|
||||
|
||||
<DeleteWidgetModal
|
||||
afterDeleteAction={afterDeleteAction}
|
||||
allowSoftDelete={!showDeletedUser}
|
||||
entityId={selectedUser?.id || ''}
|
||||
entityName={selectedUser?.name || ''}
|
||||
entityType="user"
|
||||
visible={showDeleteModal}
|
||||
onCancel={() => {
|
||||
setShowDeleteModal(false);
|
||||
setSelectedUser(undefined);
|
||||
}}
|
||||
/>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserListV1;
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright 2022 Collate
|
||||
* Licensed 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.
|
||||
*/
|
||||
|
||||
.user-listing,
|
||||
.reactive-modal {
|
||||
.ant-btn {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.user-list-table {
|
||||
.ant-table-thead > tr > th {
|
||||
font-weight: bold;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.ant-table-row .ant-table-cell:first-child,
|
||||
.ant-table-thead .ant-table-cell:first-child {
|
||||
padding-left: 16px;
|
||||
}
|
||||
|
||||
table {
|
||||
border: 1px solid #dde3ea;
|
||||
box-shadow: 1px 1px 3px 0px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
}
|
||||
}
|
@ -14,7 +14,7 @@
|
||||
import { Modal, Radio, RadioChangeEvent } from 'antd';
|
||||
import { AxiosError, AxiosResponse } from 'axios';
|
||||
import { startCase } from 'lodash';
|
||||
import React, { ChangeEvent, useCallback, useState } from 'react';
|
||||
import React, { ChangeEvent, useCallback, useEffect, useState } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import { deleteEntity } from '../../../axiosAPIs/miscAPI';
|
||||
import { ENTITY_DELETE_STATE } from '../../../constants/entity.constants';
|
||||
@ -28,6 +28,7 @@ import Loader from '../../Loader/Loader';
|
||||
import { DeleteType, DeleteWidgetModalProps } from './DeleteWidget.interface';
|
||||
|
||||
const DeleteWidgetV1 = ({
|
||||
allowSoftDelete = true,
|
||||
visible,
|
||||
entityName,
|
||||
entityType,
|
||||
@ -40,7 +41,9 @@ const DeleteWidgetV1 = ({
|
||||
const [entityDeleteState, setEntityDeleteState] =
|
||||
useState<typeof ENTITY_DELETE_STATE>(ENTITY_DELETE_STATE);
|
||||
const [name, setName] = useState<string>('');
|
||||
const [value, setValue] = useState<DeleteType>(DeleteType.SOFT_DELETE);
|
||||
const [value, setValue] = useState<DeleteType>(
|
||||
allowSoftDelete ? DeleteType.SOFT_DELETE : DeleteType.HARD_DELETE
|
||||
);
|
||||
|
||||
const prepareDeleteMessage = (softDelete = false) => {
|
||||
const softDeleteText = `Soft deleting will deactivate the ${entityName}. This will disable any discovery, read or write operations on ${entityName}`;
|
||||
@ -54,11 +57,13 @@ const DeleteWidgetV1 = ({
|
||||
title: `Delete ${entityType} “${entityName}”`,
|
||||
description: prepareDeleteMessage(true),
|
||||
type: DeleteType.SOFT_DELETE,
|
||||
isAllowd: allowSoftDelete,
|
||||
},
|
||||
{
|
||||
title: `Permanently Delete ${entityType} “${entityName}”`,
|
||||
description: prepareDeleteMessage(),
|
||||
type: DeleteType.HARD_DELETE,
|
||||
isAllowd: true,
|
||||
},
|
||||
];
|
||||
|
||||
@ -154,6 +159,14 @@ const DeleteWidgetV1 = ({
|
||||
handleOnEntityDelete(value === DeleteType.SOFT_DELETE);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setValue(allowSoftDelete ? DeleteType.SOFT_DELETE : DeleteType.HARD_DELETE);
|
||||
setEntityDeleteState({
|
||||
...ENTITY_DELETE_STATE,
|
||||
softDelete: allowSoftDelete,
|
||||
});
|
||||
}, [allowSoftDelete]);
|
||||
|
||||
const Footer = () => {
|
||||
return (
|
||||
<div className="tw-justify-end" data-testid="footer">
|
||||
@ -202,17 +215,22 @@ const DeleteWidgetV1 = ({
|
||||
visible={visible}
|
||||
onCancel={handleOnEntityDeleteCancel}>
|
||||
<Radio.Group value={value} onChange={onChange}>
|
||||
{DELETE_OPTION.map((option) => (
|
||||
<Radio
|
||||
data-testid={option.type}
|
||||
key={option.type}
|
||||
value={option.type}>
|
||||
<p className="tw-text-sm tw-mb-1 tw-font-medium">{option.title}</p>
|
||||
<p className="tw-text-grey-muted tw-text-xs tw-mb-2">
|
||||
{option.description}
|
||||
</p>
|
||||
</Radio>
|
||||
))}
|
||||
{DELETE_OPTION.map(
|
||||
(option) =>
|
||||
option.isAllowd && (
|
||||
<Radio
|
||||
data-testid={option.type}
|
||||
key={option.type}
|
||||
value={option.type}>
|
||||
<p className="tw-text-sm tw-mb-1 tw-font-medium">
|
||||
{option.title}
|
||||
</p>
|
||||
<p className="tw-text-grey-muted tw-text-xs tw-mb-2">
|
||||
{option.description}
|
||||
</p>
|
||||
</Radio>
|
||||
)
|
||||
)}
|
||||
</Radio.Group>
|
||||
<div>
|
||||
<p className="tw-mb-2">
|
||||
|
@ -14,7 +14,7 @@
|
||||
export const GLOBAL_SETTINGS_MENU = [
|
||||
{
|
||||
category: 'Access',
|
||||
items: ['Teams', 'Users', 'Roles'],
|
||||
items: ['Teams', 'Users', 'Admins', 'Roles'],
|
||||
},
|
||||
{
|
||||
category: 'Services',
|
||||
@ -26,7 +26,7 @@ export const GLOBAL_SETTINGS_MENU = [
|
||||
},
|
||||
{
|
||||
category: 'Integrations',
|
||||
items: ['Webhook', 'Slack', 'Bots'],
|
||||
items: ['Webhook', 'Bots'],
|
||||
},
|
||||
];
|
||||
|
||||
@ -47,6 +47,7 @@ export enum GlobalSettingsMenuCategory {
|
||||
|
||||
export enum GlobalSettingOptions {
|
||||
USERS = 'users',
|
||||
ADMINS = 'admins',
|
||||
TEAMS = 'teams',
|
||||
ROLES = 'roles',
|
||||
POLICIES = 'policies',
|
||||
|
@ -153,6 +153,8 @@ const jsonData = {
|
||||
'delete-glossary-success': 'Glossary deleted successfully!',
|
||||
'delete-glossary-term-success': 'Glossary term deleted successfully!',
|
||||
'test-connection-success': 'Connection tested successfully!',
|
||||
|
||||
'user-restored-success': 'User restored successfully!',
|
||||
},
|
||||
'form-error-messages': {
|
||||
'empty-email': 'Email is required.',
|
||||
|
@ -0,0 +1,212 @@
|
||||
/*
|
||||
* Copyright 2022 Collate
|
||||
* Licensed 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 { AxiosError } from 'axios';
|
||||
import { FormattedUsersData, SearchResponse } from 'Models';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { searchData } from '../../axiosAPIs/miscAPI';
|
||||
import { getUsers } from '../../axiosAPIs/userAPI';
|
||||
import Loader from '../../components/Loader/Loader';
|
||||
import UserListV1 from '../../components/UserList/UserListV1';
|
||||
import { WILD_CARD_CHAR } from '../../constants/char.constants';
|
||||
import {
|
||||
INITIAL_PAGIN_VALUE,
|
||||
PAGE_SIZE,
|
||||
pagingObject,
|
||||
} from '../../constants/constants';
|
||||
import { GlobalSettingOptions } from '../../constants/globalSettings.constants';
|
||||
import { SearchIndex } from '../../enums/search.enum';
|
||||
import { User } from '../../generated/entity/teams/user';
|
||||
import { Include } from '../../generated/type/include';
|
||||
import { Paging } from '../../generated/type/paging';
|
||||
import jsonData from '../../jsons/en';
|
||||
import { formatUsersResponse } from '../../utils/APIUtils';
|
||||
import { showErrorToast } from '../../utils/ToastUtils';
|
||||
|
||||
const teamsAndUsers = [GlobalSettingOptions.USERS, GlobalSettingOptions.ADMINS];
|
||||
|
||||
const UserListPageV1 = () => {
|
||||
const { tab } = useParams<{ [key: string]: GlobalSettingOptions }>();
|
||||
const [isAdminPage, setIsAdminPage] = useState<boolean | undefined>(
|
||||
tab === GlobalSettingOptions.ADMINS || undefined
|
||||
);
|
||||
const [isPageLoading, setIsPageLoading] = useState<boolean>(true);
|
||||
const [isDataLoading, setIsDataLoading] = useState<boolean>(true);
|
||||
const [showDeletedUser, setShowDeletedUser] = useState<boolean>(false);
|
||||
const [userList, setUserList] = useState<User[]>([]);
|
||||
const [paging, setPaging] = useState<Paging>(pagingObject);
|
||||
const [searchValue, setSearchValue] = useState<string>('');
|
||||
const [currentPage, setCurrentPage] = useState<number>(INITIAL_PAGIN_VALUE);
|
||||
|
||||
const initialSetup = () => {
|
||||
setIsAdminPage(tab === GlobalSettingOptions.ADMINS || undefined);
|
||||
setIsPageLoading(true);
|
||||
setIsDataLoading(true);
|
||||
setShowDeletedUser(false);
|
||||
setSearchValue('');
|
||||
setCurrentPage(INITIAL_PAGIN_VALUE);
|
||||
};
|
||||
|
||||
const fetchUsersList = async (
|
||||
isAdmin: boolean | undefined = undefined,
|
||||
param = {} as Record<string, string>,
|
||||
limit = PAGE_SIZE
|
||||
) => {
|
||||
setIsDataLoading(true);
|
||||
try {
|
||||
const { data } = await getUsers(
|
||||
'profile,teams,roles',
|
||||
limit,
|
||||
param,
|
||||
isAdmin,
|
||||
false
|
||||
);
|
||||
if (data) {
|
||||
setUserList(data.data);
|
||||
setPaging(data.paging);
|
||||
} else {
|
||||
throw jsonData['api-error-messages']['fetch-users-error'];
|
||||
}
|
||||
} catch (error) {
|
||||
showErrorToast(
|
||||
error as AxiosError,
|
||||
jsonData['api-error-messages']['fetch-users-error']
|
||||
);
|
||||
}
|
||||
setIsDataLoading(false);
|
||||
setIsPageLoading(false);
|
||||
};
|
||||
|
||||
const handleFetch = () => {
|
||||
fetchUsersList(isAdminPage, {
|
||||
include: showDeletedUser ? Include.Deleted : Include.NonDeleted,
|
||||
});
|
||||
};
|
||||
|
||||
const userQuerySearch = (
|
||||
text = WILD_CARD_CHAR,
|
||||
currentPage = 1,
|
||||
isAdmin = false,
|
||||
isDeleted = false
|
||||
) => {
|
||||
let filters = '';
|
||||
if (isAdmin) {
|
||||
filters = '(isAdmin:true)';
|
||||
}
|
||||
|
||||
return new Promise<Array<FormattedUsersData>>((resolve) => {
|
||||
searchData(
|
||||
text,
|
||||
currentPage,
|
||||
PAGE_SIZE,
|
||||
filters,
|
||||
'',
|
||||
'',
|
||||
SearchIndex.USER,
|
||||
isDeleted
|
||||
)
|
||||
.then((res: SearchResponse) => {
|
||||
const data = formatUsersResponse(res.data.hits.hits);
|
||||
setPaging({
|
||||
total: res.data.hits.total.value,
|
||||
});
|
||||
resolve(data);
|
||||
})
|
||||
.catch((err: AxiosError) => {
|
||||
showErrorToast(
|
||||
err,
|
||||
jsonData['api-error-messages']['fetch-users-error']
|
||||
);
|
||||
resolve([]);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const getSearchedUsers = (value: string, pageNumber: number) => {
|
||||
setIsDataLoading(true);
|
||||
|
||||
userQuerySearch(value, pageNumber, isAdminPage, showDeletedUser).then(
|
||||
(resUsers) => {
|
||||
setUserList(resUsers as unknown as User[]);
|
||||
setIsDataLoading(false);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const handlePagingChange = (
|
||||
cursorValue: string | number,
|
||||
activePage?: number
|
||||
) => {
|
||||
if (searchValue) {
|
||||
setCurrentPage(cursorValue as number);
|
||||
getSearchedUsers(searchValue, cursorValue as number);
|
||||
} else {
|
||||
setCurrentPage(activePage as number);
|
||||
fetchUsersList(isAdminPage, {
|
||||
[cursorValue]: paging[cursorValue as keyof Paging] as string,
|
||||
include: showDeletedUser ? Include.Deleted : Include.NonDeleted,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleShowDeletedUserChange = (value: boolean) => {
|
||||
setCurrentPage(INITIAL_PAGIN_VALUE);
|
||||
setSearchValue('');
|
||||
setShowDeletedUser(value);
|
||||
fetchUsersList(isAdminPage, {
|
||||
include: value ? Include.Deleted : Include.NonDeleted,
|
||||
});
|
||||
};
|
||||
|
||||
const handleSearch = (value: string) => {
|
||||
setSearchValue(value);
|
||||
setCurrentPage(INITIAL_PAGIN_VALUE);
|
||||
if (value) {
|
||||
getSearchedUsers(value, INITIAL_PAGIN_VALUE);
|
||||
} else {
|
||||
handleFetch();
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
initialSetup();
|
||||
if (teamsAndUsers.includes(tab)) {
|
||||
fetchUsersList(tab === GlobalSettingOptions.ADMINS || undefined);
|
||||
} else {
|
||||
setIsPageLoading(false);
|
||||
setIsDataLoading(false);
|
||||
}
|
||||
}, [tab]);
|
||||
|
||||
if (isPageLoading) {
|
||||
return <Loader />;
|
||||
}
|
||||
|
||||
return (
|
||||
<UserListV1
|
||||
afterDeleteAction={handleFetch}
|
||||
currentPage={currentPage}
|
||||
data={userList}
|
||||
isDataLoading={isDataLoading}
|
||||
paging={paging}
|
||||
searchTerm={searchValue}
|
||||
showDeletedUser={showDeletedUser}
|
||||
onPagingChange={handlePagingChange}
|
||||
onSearch={handleSearch}
|
||||
onShowDeletedUserChange={handleShowDeletedUserChange}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default UserListPageV1;
|
@ -34,6 +34,12 @@ const CustomPropertiesPageV1 = withSuspenseFallback(
|
||||
() => import('../pages/CustomPropertiesPage/CustomPropertiesPageV1')
|
||||
)
|
||||
);
|
||||
const RolesPageComponent = withSuspenseFallback(
|
||||
React.lazy(() => import('../pages/RolesPage/RolesPage.component'))
|
||||
);
|
||||
const UserListPageV1 = withSuspenseFallback(
|
||||
React.lazy(() => import('../pages/UserListPage/UserListPageV1'))
|
||||
);
|
||||
|
||||
const GlobalSettingRouter = () => {
|
||||
return (
|
||||
@ -46,6 +52,27 @@ const GlobalSettingRouter = () => {
|
||||
)}
|
||||
/>
|
||||
</Route>
|
||||
<Route
|
||||
exact
|
||||
path={getSettingPath(
|
||||
GlobalSettingsMenuCategory.ACCESS,
|
||||
GlobalSettingOptions.TEAMS
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
component={RolesPageComponent}
|
||||
path={getSettingPath(
|
||||
GlobalSettingsMenuCategory.ACCESS,
|
||||
GlobalSettingOptions.ROLES
|
||||
)}
|
||||
/>
|
||||
<Route
|
||||
exact
|
||||
component={UserListPageV1}
|
||||
path={getSettingCategoryPath(GlobalSettingsMenuCategory.ACCESS)}
|
||||
/>
|
||||
|
||||
<Route
|
||||
exact
|
||||
component={WebhooksPageV1}
|
||||
|
@ -11,6 +11,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Popover } from 'antd';
|
||||
import { AxiosError, AxiosResponse } from 'axios';
|
||||
import classNames from 'classnames';
|
||||
import { capitalize, isEmpty, isNil, isNull, isUndefined } from 'lodash';
|
||||
@ -612,7 +613,9 @@ export const getEntityPlaceHolder = (value: string, isDeleted?: boolean) => {
|
||||
* @param entity - entity reference
|
||||
* @returns - entity name
|
||||
*/
|
||||
export const getEntityName = (entity?: EntityReference | DataService) => {
|
||||
export const getEntityName = (
|
||||
entity?: EntityReference | DataService | User
|
||||
) => {
|
||||
return entity?.displayName || entity?.name || '';
|
||||
};
|
||||
|
||||
@ -729,3 +732,32 @@ export const isAllowedHost = () => {
|
||||
export const showPagination = (paging: Paging) => {
|
||||
return !isNil(paging.after) || !isNil(paging.before);
|
||||
};
|
||||
|
||||
export const getTeamsText = (teams: EntityReference[]) => {
|
||||
return teams.length === 0 ? (
|
||||
'No teams'
|
||||
) : teams.length > 1 ? (
|
||||
<span>
|
||||
{getEntityName(teams[0])}, &{' '}
|
||||
<Popover
|
||||
content={
|
||||
<span>
|
||||
{teams.map((t, i) => {
|
||||
return i >= 1 ? (
|
||||
<span className="tw-block tw-text-left" key={i}>
|
||||
{getEntityName(t)}
|
||||
</span>
|
||||
) : null;
|
||||
})}
|
||||
</span>
|
||||
}
|
||||
trigger="hover">
|
||||
<span className="tw-underline tw-cursor-pointer">
|
||||
{teams.length - 1} more
|
||||
</span>
|
||||
</Popover>
|
||||
</span>
|
||||
) : (
|
||||
`${getEntityName(teams[0])}`
|
||||
);
|
||||
};
|
||||
|
@ -89,6 +89,7 @@ import IconMyData from '../assets/svg/ic-mydata.svg';
|
||||
import IconQuality from '../assets/svg/ic-quality.svg';
|
||||
import IconReply from '../assets/svg/ic-reply.svg';
|
||||
import IconReports from '../assets/svg/ic-reports.svg';
|
||||
import IconRestore from '../assets/svg/ic-restore.svg';
|
||||
import IconSchema from '../assets/svg/ic-schema.svg';
|
||||
import IconSearch from '../assets/svg/ic-search.svg';
|
||||
import IconSettings from '../assets/svg/ic-settings.svg';
|
||||
@ -206,6 +207,7 @@ export const Icons = {
|
||||
QUALITY: 'icon-quality',
|
||||
ISSUES: 'icon-issues',
|
||||
TRENDS: 'icon-trends',
|
||||
RESTORE: 'icon-restore',
|
||||
LINEAGE: 'icon-lineage',
|
||||
MANAGE: 'icon-manage',
|
||||
HOME: 'icon-home',
|
||||
@ -362,6 +364,10 @@ const SVGIcons: FunctionComponent<Props> = ({
|
||||
case Icons.TEAMS:
|
||||
IconComponent = IconTeams;
|
||||
|
||||
break;
|
||||
case Icons.RESTORE:
|
||||
IconComponent = IconRestore;
|
||||
|
||||
break;
|
||||
case Icons.TEAMS_GREY:
|
||||
IconComponent = IconTeamsGrey;
|
||||
|
Loading…
x
Reference in New Issue
Block a user