diff --git a/openmetadata-ui/src/main/resources/ui/src/components/CustomEntityDetail/CustomEntityDetail.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/CustomEntityDetail/CustomEntityDetail.test.tsx deleted file mode 100644 index e6c642f7627..00000000000 --- a/openmetadata-ui/src/main/resources/ui/src/components/CustomEntityDetail/CustomEntityDetail.test.tsx +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Copyright 2021 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 { fireEvent, render } from '@testing-library/react'; -import React from 'react'; -import { MemoryRouter } from 'react-router-dom'; -import { Type } from '../../generated/entity/type'; -import { Tab } from '../common/TabsPane/TabsPane'; -import CustomEntityDetail from './CustomEntityDetail'; - -const mockData = { - id: '32f81349-d7d7-4a6a-8fc7-d767f233b674', - name: 'table', - fullyQualifiedName: 'table', - displayName: 'table', - description: - // eslint-disable-next-line max-len - '"This schema defines the Table entity. A Table organizes data in rows and columns and is defined by a Schema. OpenMetadata does not have a separate abstraction for Schema. Both Table and Schema are captured in this entity."', - category: 'entity', - nameSpace: 'data', - schema: '', - version: 0.1, - updatedAt: 1653626359971, - updatedBy: 'admin', - href: 'http://localhost:8585/api/v1/metadata/types/32f81349-d7d7-4a6a-8fc7-d767f233b674', -} as Type; - -const mockPush = jest.fn(); - -const MOCK_HISTORY = { - push: mockPush, -}; - -jest.mock('react-router-dom', () => ({ - useHistory: jest.fn().mockImplementation(() => MOCK_HISTORY), -})); - -jest.mock('../../axiosAPIs/metadataTypeAPI', () => ({ - getTypeByFQN: jest - .fn() - .mockImplementation(() => Promise.resolve({ data: mockData })), -})); - -jest.mock('../containers/PageContainer', () => { - return jest - .fn() - .mockImplementation(({ children }: { children: React.ReactNode }) => ( -
{children}
- )); -}); - -jest.mock('../../constants/constants', () => ({ - getAddCustomPropertyPath: jest.fn().mockReturnValue('/custom-entity/table'), -})); - -jest.mock('../common/TabsPane/TabsPane', () => - jest.fn().mockImplementation(({ setActiveTab, tabs }) => { - return ( -
- -
- ); - }) -); - -jest.mock('../schema-editor/SchemaEditor', () => - jest - .fn() - .mockReturnValue(
Schema Editor
) -); - -jest.mock('./CustomPropertyTable', () => ({ - CustomPropertyTable: jest - .fn() - .mockReturnValue( -
CustomPropertyTable
- ), -})); - -jest.mock('./LeftPanel', () => ({ - LeftPanel: jest - .fn() - .mockReturnValue(
LeftPanel
), -})); - -describe('Test Custom Entity Detail Component', () => { - it('Should render custom entity component', async () => { - const { findByTestId } = render( - , - { - wrapper: MemoryRouter, - } - ); - - const leftPanel = await findByTestId('LeftPanel'); - const tabContainer = await findByTestId('tabs'); - - const schemaTab = await findByTestId('Schema'); - const customPropertiesTab = await findByTestId('Custom Properties'); - - expect(leftPanel).toBeInTheDocument(); - expect(tabContainer).toBeInTheDocument(); - expect(schemaTab).toBeInTheDocument(); - expect(customPropertiesTab).toBeInTheDocument(); - }); - - it('Should render custom fields table if active tab is Custom Fields', async () => { - const { findByTestId } = render( - , - { - wrapper: MemoryRouter, - } - ); - - const leftPanel = await findByTestId('LeftPanel'); - const tabContainer = await findByTestId('tabs'); - - expect(leftPanel).toBeInTheDocument(); - expect(tabContainer).toBeInTheDocument(); - - const customPropertiesTab = await findByTestId('Custom Properties'); - - expect(customPropertiesTab).toBeInTheDocument(); - - fireEvent.click(customPropertiesTab); - - expect(await findByTestId('CustomPropertyTable')).toBeInTheDocument(); - }); - - it('Should call history.push method on click of Add field button', async () => { - const { findByTestId } = render( - , - { - wrapper: MemoryRouter, - } - ); - - const tabContainer = await findByTestId('tabs'); - - expect(tabContainer).toBeInTheDocument(); - - const customPropertiesTab = await findByTestId('Custom Properties'); - - expect(customPropertiesTab).toBeInTheDocument(); - - fireEvent.click(customPropertiesTab); - - expect(await findByTestId('CustomPropertyTable')).toBeInTheDocument(); - - const addFieldButton = await findByTestId('add-field-button'); - - expect(addFieldButton).toBeInTheDocument(); - - fireEvent.click(addFieldButton); - - expect(mockPush).toHaveBeenCalled(); - }); -}); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/CustomEntityDetail/CustomEntityDetail.tsx b/openmetadata-ui/src/main/resources/ui/src/components/CustomEntityDetail/CustomEntityDetail.tsx deleted file mode 100644 index 3ee1b460b71..00000000000 --- a/openmetadata-ui/src/main/resources/ui/src/components/CustomEntityDetail/CustomEntityDetail.tsx +++ /dev/null @@ -1,181 +0,0 @@ -/* - * Copyright 2021 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 { compare } from 'fast-json-patch'; -import { isEmpty } from 'lodash'; -import React, { FC, useEffect, useState } from 'react'; -import { useHistory } from 'react-router-dom'; -import { getTypeByFQN, updateType } from '../../axiosAPIs/metadataTypeAPI'; -import { getAddCustomPropertyPath } from '../../constants/constants'; -import { Type } from '../../generated/entity/type'; -import jsonData from '../../jsons/en'; -import { showErrorToast } from '../../utils/ToastUtils'; -import { Button } from '../buttons/Button/Button'; -import ErrorPlaceHolder from '../common/error-with-placeholder/ErrorPlaceHolder'; -import TabsPane from '../common/TabsPane/TabsPane'; -import PageContainer from '../containers/PageContainer'; -import PageLayout from '../containers/PageLayout'; -import SchemaEditor from '../schema-editor/SchemaEditor'; -import { CustomPropertyTable } from './CustomPropertyTable'; -import { LeftPanel } from './LeftPanel'; - -interface Props { - entityTypes: Array; - entityTypeFQN?: string; -} - -const CustomEntityDetail: FC = ({ entityTypes, entityTypeFQN }) => { - const history = useHistory(); - - const [activeTab, setActiveTab] = useState(1); - const [selectedEntityType, setSelectedEntityType] = useState( - {} as Type - ); - const [selectedEntityTypeDetail, setSelectedEntityTypeDetail] = - useState({} as Type); - - const fetchTypeDetail = (typeFQN: string) => { - getTypeByFQN(typeFQN) - .then((res) => { - setSelectedEntityTypeDetail(res); - }) - .catch((err: AxiosError) => showErrorToast(err)); - }; - - const onTabChange = (tab: number) => { - setActiveTab(tab); - }; - - const onEntityTypeSelect = (entityType: Type) => { - setSelectedEntityType(entityType); - }; - - const handleAddProperty = () => { - const path = getAddCustomPropertyPath( - selectedEntityTypeDetail.fullyQualifiedName as string - ); - history.push(path); - }; - - const schemaCheck = activeTab === 2 && !isEmpty(selectedEntityTypeDetail); - const schemaValue = selectedEntityTypeDetail.schema || '{}'; - - const customPropertiesCheck = - activeTab === 1 && !isEmpty(selectedEntityTypeDetail); - const customProperties = selectedEntityTypeDetail.customProperties || []; - - const tabs = [ - { - name: 'Custom Properties', - isProtected: false, - position: 1, - count: customProperties.length, - }, - { - name: 'Schema', - isProtected: false, - position: 2, - }, - ]; - - const componentCheck = Boolean(entityTypes.length); - - const updateEntityType = (properties: Type['customProperties']) => { - const patch = compare(selectedEntityTypeDetail, { - ...selectedEntityTypeDetail, - customProperties: properties, - }); - - updateType(selectedEntityTypeDetail.id as string, patch) - .then((res) => { - const { customProperties: properties } = res; - - setSelectedEntityTypeDetail((prev) => ({ - ...prev, - customProperties: properties, - })); - }) - .catch((err: AxiosError) => showErrorToast(err)); - }; - - useEffect(() => { - if (entityTypes.length) { - const entityType = - entityTypes.find((type) => type.fullyQualifiedName === entityTypeFQN) || - entityTypes[0]; - onEntityTypeSelect(entityType); - } - }, [entityTypes, entityTypeFQN]); - - useEffect(() => { - if (!isEmpty(selectedEntityType)) { - fetchTypeDetail(selectedEntityType.fullyQualifiedName as string); - } - }, [selectedEntityType]); - - return ( - - {componentCheck ? ( - - }> - -
- {schemaCheck && ( -
- -
- )} - {customPropertiesCheck && ( -
-
- -
- -
- )} -
-
- ) : ( - - {jsonData['message']['no-custom-entity']} - - )} -
- ); -}; - -export default CustomEntityDetail; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/CustomEntityDetail/CustomPropertyTable.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/CustomEntityDetail/CustomPropertyTable.test.tsx index f55743a8604..f087cd22a45 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/CustomEntityDetail/CustomPropertyTable.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/CustomEntityDetail/CustomPropertyTable.test.tsx @@ -60,6 +60,7 @@ const mockProperties = [ ]; const mockProp = { + hasAccess: true, customProperties: mockProperties, updateEntityType: mockUpdateEntityType, }; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/CustomEntityDetail/CustomPropertyTable.tsx b/openmetadata-ui/src/main/resources/ui/src/components/CustomEntityDetail/CustomPropertyTable.tsx index b696141acc0..56f5416fd04 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/CustomEntityDetail/CustomPropertyTable.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/CustomEntityDetail/CustomPropertyTable.tsx @@ -11,9 +11,11 @@ * limitations under the License. */ +import { Tooltip } from 'antd'; import classNames from 'classnames'; import { isEmpty, uniqueId } from 'lodash'; import React, { FC, Fragment, useState } from 'react'; +import { NO_PERMISSION_FOR_ACTION } from '../../constants/HelperTextUtil'; import { CustomProperty, Type } from '../../generated/entity/type'; import { getEntityName, isEven } from '../../utils/CommonUtils'; import SVGIcons, { Icons } from '../../utils/SvgUtils'; @@ -22,6 +24,7 @@ import ConfirmationModal from '../Modals/ConfirmationModal/ConfirmationModal'; import { ModalWithMarkdownEditor } from '../Modals/ModalWithMarkdownEditor/ModalWithMarkdownEditor'; interface CustomPropertyTableProp { + hasAccess: boolean; customProperties: CustomProperty[]; updateEntityType: (customProperties: Type['customProperties']) => void; } @@ -31,6 +34,7 @@ type Operation = 'delete' | 'update' | 'no-operation'; export const CustomPropertyTable: FC = ({ customProperties, updateEntityType, + hasAccess, }) => { const [selectedProperty, setSelectedProperty] = useState( {} as CustomProperty @@ -120,34 +124,42 @@ export const CustomPropertyTable: FC = ({
- - + + + + + +
diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/CustomPropertiesPageV1/CustomPropertiesPageV1.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/CustomPropertiesPageV1/CustomPropertiesPageV1.tsx index 4d8e93a8c95..46d6373cec5 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/CustomPropertiesPageV1/CustomPropertiesPageV1.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/CustomPropertiesPageV1/CustomPropertiesPageV1.tsx @@ -11,10 +11,10 @@ * limitations under the License. */ -import { Col, Empty, Row } from 'antd'; +import { Col, Empty, Row, Tooltip } from 'antd'; import { AxiosError } from 'axios'; import { compare } from 'fast-json-patch'; -import { isEmpty, isUndefined } from 'lodash'; +import { isUndefined } from 'lodash'; import React, { useEffect, useMemo, useState } from 'react'; import { useHistory, useParams } from 'react-router-dom'; import { getTypeByFQN, updateType } from '../../axiosAPIs/metadataTypeAPI'; @@ -24,15 +24,20 @@ import TabsPane from '../../components/common/TabsPane/TabsPane'; import { CustomPropertyTable } from '../../components/CustomEntityDetail/CustomPropertyTable'; import Loader from '../../components/Loader/Loader'; import { usePermissionProvider } from '../../components/PermissionProvider/PermissionProvider'; +import { + OperationPermission, + ResourceEntity, +} from '../../components/PermissionProvider/PermissionProvider.interface'; import SchemaEditor from '../../components/schema-editor/SchemaEditor'; import { getAddCustomPropertyPath } from '../../constants/constants'; import { customAttributesPath } from '../../constants/globalSettings.constants'; -import { NO_PERMISSION_TO_VIEW } from '../../constants/HelperTextUtil'; -import { Operation } from '../../generated/entity/policies/policy'; +import { + NO_PERMISSION_FOR_ACTION, + NO_PERMISSION_TO_VIEW, +} from '../../constants/HelperTextUtil'; import { Type } from '../../generated/entity/type'; import jsonData from '../../jsons/en'; -import { getResourceEntityFromCustomProperty } from '../../utils/CustomPropertyUtils'; -import { checkPermission } from '../../utils/PermissionsUtils'; +import { DEFAULT_ENTITY_PERMISSION } from '../../utils/PermissionsUtils'; import { showErrorToast } from '../../utils/ToastUtils'; import './CustomPropertiesPageV1.less'; @@ -49,18 +54,32 @@ const CustomEntityDetailV1 = () => { const tabAttributePath = customAttributesPath[tab as keyof typeof customAttributesPath]; - const { permissions } = usePermissionProvider(); + const { getEntityPermission } = usePermissionProvider(); - const viewPermission = useMemo(() => { - return ( - !isEmpty(permissions) && - checkPermission( - Operation.ViewAll, - getResourceEntityFromCustomProperty(tab), - permissions - ) - ); - }, [permissions, tab]); + const [propertyPermission, setPropertyPermission] = + useState(DEFAULT_ENTITY_PERMISSION); + + const fetchPermission = async () => { + try { + const response = await getEntityPermission( + ResourceEntity.TYPE, + selectedEntityTypeDetail.id as string + ); + setPropertyPermission(response); + } catch (error) { + showErrorToast(error as AxiosError); + } + }; + + const viewPermission = useMemo( + () => propertyPermission.ViewAll, + [propertyPermission, tab] + ); + + const editPermission = useMemo( + () => propertyPermission.EditAll, + [propertyPermission, tab] + ); const fetchTypeDetail = async (typeFQN: string) => { setIsLoading(true); @@ -126,6 +145,12 @@ const CustomEntityDetailV1 = () => { } }, [tab]); + useEffect(() => { + if (selectedEntityTypeDetail?.id) { + fetchPermission(); + } + }, [selectedEntityTypeDetail]); + if (isLoading) { return ; } @@ -163,17 +188,22 @@ const CustomEntityDetailV1 = () => { {activeTab === 1 && (
- + + +
diff --git a/openmetadata-ui/src/main/resources/ui/src/router/AuthenticatedAppRouter.tsx b/openmetadata-ui/src/main/resources/ui/src/router/AuthenticatedAppRouter.tsx index 5d6d6621a46..35af00124e9 100644 --- a/openmetadata-ui/src/main/resources/ui/src/router/AuthenticatedAppRouter.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/router/AuthenticatedAppRouter.tsx @@ -466,14 +466,9 @@ const AuthenticatedAppRouter: FunctionComponent = () => { )} path={ROUTES.BOTS_PROFILE} /> - { component={CustomPropertiesPageV1} hasPermission={checkPermission( Operation.ViewAll, - ResourceEntity.ALL, + ResourceEntity.TYPE, permissions )} path={getSettingCategoryPath(