mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-08-27 10:26:09 +00:00
Update Users, Admins and tags page with new roles and policy (#7050)
* Update Users, Admins and tags page with new roles and policy * Fix unit tests * Minor change
This commit is contained in:
parent
7cd5579c84
commit
c366d39737
@ -11,55 +11,28 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Menu, MenuProps } from 'antd';
|
||||
import { Empty, Menu, MenuProps } from 'antd';
|
||||
import { ItemType } from 'antd/lib/menu/hooks/useItems';
|
||||
import { AxiosError } from 'axios';
|
||||
import { camelCase } from 'lodash';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useHistory, useParams } from 'react-router-dom';
|
||||
import { GLOBAL_SETTING_PERMISSION_RESOURCES } from '../../constants/globalSettings.constants';
|
||||
import {
|
||||
getGlobalSettingMenuItem,
|
||||
getGlobalSettingsMenuWithPermission,
|
||||
MenuList,
|
||||
} from '../../utils/GlobalSettingsUtils';
|
||||
import { getSettingPath } from '../../utils/RouterUtils';
|
||||
import { showErrorToast } from '../../utils/ToastUtils';
|
||||
import Loader from '../Loader/Loader';
|
||||
import { usePermissionProvider } from '../PermissionProvider/PermissionProvider';
|
||||
import {
|
||||
ResourceEntity,
|
||||
UIPermission,
|
||||
} from '../PermissionProvider/PermissionProvider.interface';
|
||||
|
||||
const GlobalSettingLeftPanel = () => {
|
||||
const history = useHistory();
|
||||
const { tab, settingCategory } = useParams<{ [key: string]: string }>();
|
||||
const [settingResourcePermission, setSettingResourcePermission] =
|
||||
useState<UIPermission>({} as UIPermission);
|
||||
|
||||
const [isLoading, setIsLoading] = useState<boolean>(true);
|
||||
|
||||
const { getResourcePermission } = usePermissionProvider();
|
||||
|
||||
const fetchResourcesPermission = async (resource: ResourceEntity) => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
const response = await getResourcePermission(resource);
|
||||
setSettingResourcePermission((prev) => ({
|
||||
...prev,
|
||||
[resource]: response,
|
||||
}));
|
||||
} catch (error) {
|
||||
showErrorToast(error as AxiosError);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
const { permissions } = usePermissionProvider();
|
||||
|
||||
const menuItems: ItemType[] = useMemo(
|
||||
() =>
|
||||
getGlobalSettingsMenuWithPermission(settingResourcePermission).reduce(
|
||||
getGlobalSettingsMenuWithPermission(permissions).reduce(
|
||||
(acc: ItemType[], curr: MenuList) => {
|
||||
const menuItem = getGlobalSettingMenuItem(
|
||||
curr.category,
|
||||
@ -77,7 +50,7 @@ const GlobalSettingLeftPanel = () => {
|
||||
},
|
||||
[] as ItemType[]
|
||||
),
|
||||
[setSettingResourcePermission]
|
||||
[permissions]
|
||||
);
|
||||
|
||||
const onClick: MenuProps['onClick'] = (e) => {
|
||||
@ -86,20 +59,7 @@ const GlobalSettingLeftPanel = () => {
|
||||
history.push(getSettingPath(category, option));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
// TODO: This will make number of API calls, need to think of better solution
|
||||
GLOBAL_SETTING_PERMISSION_RESOURCES.forEach((resource) => {
|
||||
fetchResourcesPermission(resource);
|
||||
});
|
||||
}, []);
|
||||
|
||||
if (isLoading) {
|
||||
return <Loader />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{menuItems.length ? (
|
||||
return menuItems.length ? (
|
||||
<Menu
|
||||
className="global-setting-left-panel"
|
||||
items={menuItems}
|
||||
@ -107,8 +67,8 @@ const GlobalSettingLeftPanel = () => {
|
||||
selectedKeys={[`${settingCategory}.${tab}`]}
|
||||
onClick={onClick}
|
||||
/>
|
||||
) : null}
|
||||
</>
|
||||
) : (
|
||||
<Empty className="tw-mt-8" />
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -121,25 +121,12 @@ const PermissionProvider: FC<PermissionProviderProps> = ({ children }) => {
|
||||
[resource]: operationPermission,
|
||||
}));
|
||||
|
||||
/**
|
||||
* Store updated resource permission
|
||||
*/
|
||||
setPermissions((prev) => ({
|
||||
...prev,
|
||||
[resource]: operationPermission,
|
||||
}));
|
||||
|
||||
return operationPermission;
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
/**
|
||||
* Only fetch permission if user is logged In
|
||||
*/
|
||||
if (currentUser && currentUser.id) {
|
||||
fetchLoggedInUserPermissions();
|
||||
}
|
||||
}, [currentUser]);
|
||||
|
||||
return (
|
||||
|
@ -19,17 +19,22 @@ 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 { NO_PERMISSION_FOR_ACTION } from '../../constants/HelperTextUtil';
|
||||
import { CreateUser } from '../../generated/api/teams/createUser';
|
||||
import { Operation } from '../../generated/entity/policies/policy';
|
||||
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 { checkPermission } from '../../utils/PermissionsUtils';
|
||||
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 { usePermissionProvider } from '../PermissionProvider/PermissionProvider';
|
||||
import { ResourceEntity } from '../PermissionProvider/PermissionProvider.interface';
|
||||
import './usersList.less';
|
||||
|
||||
interface UserListV1Props {
|
||||
@ -57,12 +62,23 @@ const UserListV1: FC<UserListV1Props> = ({
|
||||
onPagingChange,
|
||||
afterDeleteAction,
|
||||
}) => {
|
||||
const { permissions } = usePermissionProvider();
|
||||
const history = useHistory();
|
||||
const [selectedUser, setSelectedUser] = useState<User>();
|
||||
const [showDeleteModal, setShowDeleteModal] = useState(false);
|
||||
const [showReactiveModal, setShowReactiveModal] = useState(false);
|
||||
const showRestore = showDeletedUser && !isDataLoading;
|
||||
|
||||
const createPermission = useMemo(
|
||||
() => checkPermission(Operation.Create, ResourceEntity.USER, permissions),
|
||||
[permissions]
|
||||
);
|
||||
|
||||
const deletePermission = useMemo(
|
||||
() => checkPermission(Operation.Delete, ResourceEntity.USER, permissions),
|
||||
[permissions]
|
||||
);
|
||||
|
||||
const handleAddNewUser = () => {
|
||||
history.push(ROUTES.CREATE_USER);
|
||||
};
|
||||
@ -156,8 +172,11 @@ const UserListV1: FC<UserListV1Props> = ({
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
<Tooltip placement="bottom" title="Delete">
|
||||
<Tooltip
|
||||
placement="bottom"
|
||||
title={deletePermission ? 'Delete' : NO_PERMISSION_FOR_ACTION}>
|
||||
<Button
|
||||
disabled={!deletePermission}
|
||||
icon={
|
||||
<SVGIcons
|
||||
alt="Delete"
|
||||
@ -199,9 +218,15 @@ const UserListV1: FC<UserListV1Props> = ({
|
||||
/>
|
||||
<span className="tw-ml-2">Deleted Users</span>
|
||||
</span>
|
||||
<Button type="primary" onClick={handleAddNewUser}>
|
||||
<Tooltip
|
||||
title={createPermission ? 'Add User' : NO_PERMISSION_FOR_ACTION}>
|
||||
<Button
|
||||
disabled={!createPermission}
|
||||
type="primary"
|
||||
onClick={handleAddNewUser}>
|
||||
Add User
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Space>
|
||||
</Col>
|
||||
|
||||
|
@ -28,6 +28,10 @@ type Props = {
|
||||
permission?: Operation;
|
||||
};
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
* TODO: Remove this component once we have new permission structure everywhere
|
||||
*/
|
||||
const NonAdminAction = ({
|
||||
children,
|
||||
className = '',
|
||||
|
@ -190,6 +190,41 @@ const mockCategory = [
|
||||
},
|
||||
];
|
||||
|
||||
jest.mock('../../components/PermissionProvider/PermissionProvider', () => ({
|
||||
usePermissionProvider: jest.fn().mockReturnValue({
|
||||
getEntityPermission: jest.fn().mockReturnValue({
|
||||
Create: true,
|
||||
Delete: true,
|
||||
ViewAll: true,
|
||||
EditAll: true,
|
||||
EditDescription: true,
|
||||
EditDisplayName: true,
|
||||
}),
|
||||
permissions: {
|
||||
tagCategory: {
|
||||
Create: true,
|
||||
Delete: true,
|
||||
ViewAll: true,
|
||||
EditAll: true,
|
||||
EditDescription: true,
|
||||
EditDisplayName: true,
|
||||
},
|
||||
tag: {
|
||||
Create: true,
|
||||
Delete: true,
|
||||
ViewAll: true,
|
||||
EditAll: true,
|
||||
EditDescription: true,
|
||||
EditDisplayName: true,
|
||||
},
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('../../utils/PermissionsUtils', () => ({
|
||||
checkPermission: jest.fn().mockReturnValue(true),
|
||||
}));
|
||||
|
||||
jest.mock('../../axiosAPIs/tagAPI', () => ({
|
||||
createTag: jest.fn(),
|
||||
createTagCategory: jest.fn(),
|
||||
|
@ -12,14 +12,12 @@
|
||||
*/
|
||||
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { Card } from 'antd';
|
||||
import { Button, Card, Tooltip } from 'antd';
|
||||
import { AxiosError } from 'axios';
|
||||
import classNames from 'classnames';
|
||||
import { isEmpty, isUndefined, toLower } from 'lodash';
|
||||
import { FormErrorData, LoadingState } from 'Models';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { Link, useHistory, useParams } from 'react-router-dom';
|
||||
import { useAuthContext } from '../../authentication/auth-provider/AuthProvider';
|
||||
import {
|
||||
createTag,
|
||||
createTagCategory,
|
||||
@ -29,11 +27,9 @@ import {
|
||||
updateTag,
|
||||
updateTagCategory,
|
||||
} from '../../axiosAPIs/tagAPI';
|
||||
import { Button } from '../../components/buttons/Button/Button';
|
||||
import Description from '../../components/common/description/Description';
|
||||
import Ellipses from '../../components/common/Ellipses/Ellipses';
|
||||
import ErrorPlaceHolder from '../../components/common/error-with-placeholder/ErrorPlaceHolder';
|
||||
import NonAdminAction from '../../components/common/non-admin-action/NonAdminAction';
|
||||
import RichTextEditorPreviewer from '../../components/common/rich-text-editor/RichTextEditorPreviewer';
|
||||
import PageContainerV1 from '../../components/containers/PageContainerV1';
|
||||
import PageLayout, {
|
||||
@ -43,7 +39,12 @@ import Loader from '../../components/Loader/Loader';
|
||||
import ConfirmationModal from '../../components/Modals/ConfirmationModal/ConfirmationModal';
|
||||
import FormModal from '../../components/Modals/FormModal';
|
||||
import { ModalWithMarkdownEditor } from '../../components/Modals/ModalWithMarkdownEditor/ModalWithMarkdownEditor';
|
||||
import { TITLE_FOR_NON_ADMIN_ACTION } from '../../constants/constants';
|
||||
import { usePermissionProvider } from '../../components/PermissionProvider/PermissionProvider';
|
||||
import {
|
||||
OperationPermission,
|
||||
ResourceEntity,
|
||||
} from '../../components/PermissionProvider/PermissionProvider.interface';
|
||||
import { NO_PERMISSION_FOR_ACTION } from '../../constants/HelperTextUtil';
|
||||
import { delimiterRegex } from '../../constants/regex.constants';
|
||||
import {
|
||||
CreateTagCategory,
|
||||
@ -52,7 +53,6 @@ import {
|
||||
import { Operation } from '../../generated/entity/policies/accessControl/rule';
|
||||
import { TagCategory, TagClass } from '../../generated/entity/tags/tagCategory';
|
||||
import { EntityReference } from '../../generated/type/entityReference';
|
||||
import { useAuth } from '../../hooks/authHooks';
|
||||
import jsonData from '../../jsons/en';
|
||||
import {
|
||||
getActiveCatClass,
|
||||
@ -61,6 +61,7 @@ import {
|
||||
isEven,
|
||||
isUrlFriendlyName,
|
||||
} from '../../utils/CommonUtils';
|
||||
import { checkPermission } from '../../utils/PermissionsUtils';
|
||||
import {
|
||||
getExplorePathWithInitFilters,
|
||||
getTagPath,
|
||||
@ -85,10 +86,9 @@ type DeleteTagsType = {
|
||||
};
|
||||
|
||||
const TagsPage = () => {
|
||||
const { getEntityPermission, permissions } = usePermissionProvider();
|
||||
const history = useHistory();
|
||||
const { tagCategoryName } = useParams<Record<string, string>>();
|
||||
const { isAdminUser } = useAuth();
|
||||
const { isAuthDisabled } = useAuthContext();
|
||||
const [categories, setCategoreis] = useState<Array<TagCategory>>([]);
|
||||
const [currentCategory, setCurrentCategory] = useState<TagCategory>();
|
||||
const [isEditCategory, setIsEditCategory] = useState<boolean>(false);
|
||||
@ -104,6 +104,35 @@ const TagsPage = () => {
|
||||
data: undefined,
|
||||
state: false,
|
||||
});
|
||||
const [categoryPermissions, setCategoryPermissions] =
|
||||
useState<OperationPermission>({} as OperationPermission);
|
||||
|
||||
const createCategoryPermission = useMemo(
|
||||
() =>
|
||||
checkPermission(
|
||||
Operation.Create,
|
||||
ResourceEntity.TAG_CATEGORY,
|
||||
permissions
|
||||
),
|
||||
[permissions]
|
||||
);
|
||||
|
||||
const createTagPermission = useMemo(
|
||||
() => checkPermission(Operation.Create, ResourceEntity.TAG, permissions),
|
||||
[permissions]
|
||||
);
|
||||
|
||||
const fetchCurrentCategoryPermission = async () => {
|
||||
try {
|
||||
const response = await getEntityPermission(
|
||||
ResourceEntity.TAG_CATEGORY,
|
||||
currentCategory?.id as string
|
||||
);
|
||||
setCategoryPermissions(response);
|
||||
} catch (error) {
|
||||
showErrorToast(error as AxiosError);
|
||||
}
|
||||
};
|
||||
|
||||
const fetchCategories = () => {
|
||||
setIsLoading(true);
|
||||
@ -405,6 +434,7 @@ const TagsPage = () => {
|
||||
setCurrentCategory(categories[0]);
|
||||
if (currentCategory) {
|
||||
setCurrentCategory(currentCategory);
|
||||
fetchCurrentCategoryPermission();
|
||||
}
|
||||
}, [categories, currentCategory]);
|
||||
|
||||
@ -426,24 +456,25 @@ const TagsPage = () => {
|
||||
style={{ fontSize: '14px' }}>
|
||||
Tag Categories
|
||||
</span>
|
||||
<NonAdminAction
|
||||
position="bottom"
|
||||
title={TITLE_FOR_NON_ADMIN_ACTION}>
|
||||
<Tooltip
|
||||
title={
|
||||
createCategoryPermission
|
||||
? 'Add Category'
|
||||
: NO_PERMISSION_FOR_ACTION
|
||||
}>
|
||||
<Button
|
||||
className={classNames('tw-px-2 ', {
|
||||
'tw-opacity-40': !isAdminUser && !isAuthDisabled,
|
||||
})}
|
||||
className="tw-px-2 "
|
||||
data-testid="add-category"
|
||||
disabled={!createCategoryPermission}
|
||||
size="small"
|
||||
theme="primary"
|
||||
variant="contained"
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
setIsAddingCategory((prevState) => !prevState);
|
||||
setErrorDataCategory(undefined);
|
||||
}}>
|
||||
<FontAwesomeIcon icon="plus" />
|
||||
</Button>
|
||||
</NonAdminAction>
|
||||
</Tooltip>
|
||||
</div>
|
||||
}>
|
||||
<>
|
||||
@ -505,44 +536,39 @@ const TagsPage = () => {
|
||||
{currentCategory.displayName ?? currentCategory.name}
|
||||
</div>
|
||||
<div>
|
||||
<NonAdminAction
|
||||
position="bottom"
|
||||
title={TITLE_FOR_NON_ADMIN_ACTION}>
|
||||
<Tooltip
|
||||
title={
|
||||
createTagPermission || categoryPermissions.EditAll
|
||||
? 'Add Tag'
|
||||
: NO_PERMISSION_FOR_ACTION
|
||||
}>
|
||||
<Button
|
||||
className={classNames('tw-h-8 tw-rounded tw-mb-3', {
|
||||
'tw-opacity-40': !isAdminUser && !isAuthDisabled,
|
||||
})}
|
||||
className="tw-h-8 tw-rounded tw-mb-3"
|
||||
data-testid="add-new-tag-button"
|
||||
disabled={
|
||||
!createTagPermission && !categoryPermissions.EditAll
|
||||
}
|
||||
size="small"
|
||||
theme="primary"
|
||||
variant="contained"
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
setIsAddingTag((prevState) => !prevState);
|
||||
setErrorDataTag(undefined);
|
||||
}}>
|
||||
Add new tag
|
||||
</Button>
|
||||
</NonAdminAction>
|
||||
<NonAdminAction
|
||||
position="bottom"
|
||||
title={TITLE_FOR_NON_ADMIN_ACTION}>
|
||||
</Tooltip>
|
||||
|
||||
<Button
|
||||
className={classNames(
|
||||
'tw-h-8 tw-rounded tw-mb-3 tw-ml-2',
|
||||
{
|
||||
'tw-opacity-40': !isAdminUser && !isAuthDisabled,
|
||||
}
|
||||
)}
|
||||
className="tw-h-8 tw-rounded tw-mb-3 tw-ml-2"
|
||||
data-testid="delete-tag-category-button"
|
||||
disabled={!categoryPermissions.Delete}
|
||||
size="small"
|
||||
theme="primary"
|
||||
variant="outlined"
|
||||
type="primary"
|
||||
onClick={() => {
|
||||
deleteTagHandler();
|
||||
}}>
|
||||
Delete category
|
||||
</Button>
|
||||
</NonAdminAction>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@ -554,6 +580,7 @@ const TagsPage = () => {
|
||||
entityName={
|
||||
currentCategory?.displayName ?? currentCategory?.name
|
||||
}
|
||||
hasEditAccess={categoryPermissions.EditDescription}
|
||||
isEdit={isEditCategory}
|
||||
onCancel={() => setIsEditCategory(false)}
|
||||
onDescriptionEdit={() => setIsEditCategory(true)}
|
||||
@ -605,10 +632,8 @@ const TagsPage = () => {
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<NonAdminAction
|
||||
permission={Operation.EditDescription}
|
||||
position="left"
|
||||
title={TITLE_FOR_NON_ADMIN_ACTION}>
|
||||
|
||||
{categoryPermissions.EditDescription && (
|
||||
<button
|
||||
className="tw-self-start tw-w-8 tw-h-auto tw-opacity-0 tw-ml-1 group-hover:tw-opacity-100 focus:tw-outline-none"
|
||||
onClick={() => {
|
||||
@ -623,7 +648,7 @@ const TagsPage = () => {
|
||||
width="16px"
|
||||
/>
|
||||
</button>
|
||||
</NonAdminAction>
|
||||
)}
|
||||
</div>
|
||||
<div className="tw-mt-1" data-testid="usage">
|
||||
<span className="tw-text-grey-muted tw-mr-1">
|
||||
@ -649,12 +674,10 @@ const TagsPage = () => {
|
||||
</td>
|
||||
<td className="tableBody-cell">
|
||||
<div className="tw-text-center">
|
||||
<NonAdminAction
|
||||
position="bottom"
|
||||
title={TITLE_FOR_NON_ADMIN_ACTION}>
|
||||
<button
|
||||
className="link-text"
|
||||
data-testid="delete-tag"
|
||||
disabled={!categoryPermissions.EditAll}
|
||||
onClick={() =>
|
||||
setDeleteTags({
|
||||
data: {
|
||||
@ -682,7 +705,6 @@ const TagsPage = () => {
|
||||
/>
|
||||
)}
|
||||
</button>
|
||||
</NonAdminAction>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
Loading…
x
Reference in New Issue
Block a user