diff --git a/openmetadata-ui/src/main/resources/ui/cypress/common/Entities/EntityClass.ts b/openmetadata-ui/src/main/resources/ui/cypress/common/Entities/EntityClass.ts index eed12640789c..ea22c9fdce14 100644 --- a/openmetadata-ui/src/main/resources/ui/cypress/common/Entities/EntityClass.ts +++ b/openmetadata-ui/src/main/resources/ui/cypress/common/Entities/EntityClass.ts @@ -26,6 +26,7 @@ import { deleteCustomPropertyForEntity, generateCustomProperty, setValueForProperty, + validateValueForProperty, } from '../Utils/CustomProperty'; import { addDomainToEntity, removeDomainFromEntity } from '../Utils/Domain'; import { @@ -483,9 +484,12 @@ class EntityClass { setCustomProperty(propertydetails: CustomProperty, value: string) { setValueForProperty(propertydetails.name, value); + validateValueForProperty(propertydetails.name, value); } + updateCustomProperty(propertydetails: CustomProperty, value: string) { setValueForProperty(propertydetails.name, value); + validateValueForProperty(propertydetails.name, value); } } diff --git a/openmetadata-ui/src/main/resources/ui/cypress/common/Utils/CustomProperty.ts b/openmetadata-ui/src/main/resources/ui/cypress/common/Utils/CustomProperty.ts index 12c8e0aeab83..07a2324c783b 100644 --- a/openmetadata-ui/src/main/resources/ui/cypress/common/Utils/CustomProperty.ts +++ b/openmetadata-ui/src/main/resources/ui/cypress/common/Utils/CustomProperty.ts @@ -145,3 +145,16 @@ export const setValueForProperty = (propertyName, value: string) => { value.replace(/\*|_/gi, '') ); }; +export const validateValueForProperty = (propertyName, value: string) => { + cy.get('.ant-tabs-tab').first().click(); + cy.get( + '[data-testid="entity-right-panel"] [data-testid="custom-properties-table"]', + { + timeout: 10000, + } + ).scrollIntoView(); + cy.get(`[data-row-key="${propertyName}"]`).should( + 'contain', + value.replace(/\*|_/gi, '') + ); +}; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DashboardDetails/DashboardDetails.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DashboardDetails/DashboardDetails.component.tsx index c52832a4db43..7446208494dd 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DashboardDetails/DashboardDetails.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DashboardDetails/DashboardDetails.component.tsx @@ -621,6 +621,7 @@ const DashboardDetails = ({ data-testid="entity-right-panel" flex="320px"> @@ -676,13 +678,16 @@ const DashboardDetails = ({ /> ), key: EntityTabs.CUSTOM_PROPERTIES, - children: ( - + children: dashboardDetails && ( +
+ + entityDetails={dashboardDetails} + entityType={EntityType.DASHBOARD} + handleExtensionUpdate={onExtensionUpdate} + hasEditAccess={editCustomAttributePermission} + hasPermission={viewAllPermission} + /> +
), }, ], diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityRightPanel/EntityRightPanel.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityRightPanel/EntityRightPanel.test.tsx index 11a740110583..26217a53ee63 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityRightPanel/EntityRightPanel.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityRightPanel/EntityRightPanel.test.tsx @@ -14,6 +14,7 @@ import { render, screen } from '@testing-library/react'; import { EntityTags } from 'Models'; import React from 'react'; import { EntityType } from '../../../enums/entity.enum'; +import { Table } from '../../../generated/entity/data/table'; import { EntityReference } from '../../../generated/entity/type'; import entityRightPanelClassBase from '../../../utils/EntityRightPanelClassBase'; import EntityRightPanel from './EntityRightPanel'; @@ -26,18 +27,45 @@ jest.mock('../../Tag/TagsContainerV2/TagsContainerV2', () => { return jest.fn().mockImplementation(() =>
TagsContainerV2
); }); +jest.mock('../../common/CustomPropertyTable/CustomPropertyTable', () => ({ + CustomPropertyTable: jest + .fn() + .mockImplementation(() => ( +
CustomPropertyTable
+ )), +})); + jest.mock('../../../utils/EntityRightPanelClassBase'); +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useParams: jest.fn().mockImplementation(() => ({ + fqn: 'fqn', + tab: 'tab', + version: 'version', + })), + Link: jest + .fn() + .mockImplementation(({ children, ...rest }) => {children}), +})); + describe('EntityRightPanel component test', () => { const mockDataProducts: EntityReference[] = []; const mockSelectedTags: EntityTags[] = []; const mockOnTagSelectionChange = jest.fn(); const mockOnThreadLinkSelect = jest.fn(); + const mockCustomProperties = { + extension: { + test1: 'test', + test2: '', + }, + } as Table; it('Component should render', () => { render( { render( { editTagPermission afterSlot={
afterSlot
} beforeSlot={
beforeSlot
} + customProperties={mockCustomProperties} dataProducts={mockDataProducts} entityFQN="testEntityFQN" entityId="testEntityId" @@ -95,6 +125,7 @@ describe('EntityRightPanel component test', () => { render( { render( { render( { expect(screen.queryByText('KnowledgeArticles')).not.toBeInTheDocument(); }); + + it('should render CustomPropertyTable when mockCustomProperties is not null', () => { + render( + + ); + + expect( + screen.getByText('label.custom-property-plural') + ).toBeInTheDocument(); + expect( + screen.queryByText('message.no-access-placeholder') + ).not.toBeInTheDocument(); + }); + + it('should not render CustomPropertyTable when no custom properties', () => { + render( + + ); + + expect(screen.queryByText('CustomPropertyTable')).not.toBeInTheDocument(); + }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityRightPanel/EntityRightPanel.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityRightPanel/EntityRightPanel.tsx index 32d399afdcd1..007b8ba4983c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityRightPanel/EntityRightPanel.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityRightPanel/EntityRightPanel.tsx @@ -10,19 +10,27 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { Space } from 'antd'; +import { Space, Typography } from 'antd'; +import { t } from 'i18next'; import { EntityTags } from 'Models'; -import React, { FC } from 'react'; -import { EntityType } from '../../../enums/entity.enum'; +import React from 'react'; +import { Link } from 'react-router-dom'; +import { EntityTabs, EntityType } from '../../../enums/entity.enum'; import { ThreadType } from '../../../generated/entity/feed/thread'; import { EntityReference } from '../../../generated/entity/type'; import { TagSource } from '../../../generated/type/tagLabel'; +import { getEntityDetailLink } from '../../../utils/CommonUtils'; import entityRightPanelClassBase from '../../../utils/EntityRightPanelClassBase'; +import { CustomPropertyTable } from '../../common/CustomPropertyTable/CustomPropertyTable'; +import type { + ExtentionEntities, + ExtentionEntitiesKeys, +} from '../../common/CustomPropertyTable/CustomPropertyTable.interface'; import DataProductsContainer from '../../DataProductsContainer/DataProductsContainer.component'; import TagsContainerV2 from '../../Tag/TagsContainerV2/TagsContainerV2'; import { DisplayType } from '../../Tag/TagsViewer/TagsViewer.interface'; -interface EntityRightPanelProps { +interface EntityRightPanelProps { dataProducts: EntityReference[]; editTagPermission: boolean; entityType: EntityType; @@ -36,9 +44,11 @@ interface EntityRightPanelProps { domain?: EntityReference; onTagSelectionChange?: (selectedTags: EntityTags[]) => Promise; onThreadLinkSelect?: (value: string, threadType?: ThreadType) => void; + viewAllPermission?: boolean; + customProperties?: ExtentionEntities[T]; } -const EntityRightPanel: FC = ({ +const EntityRightPanel = ({ domain, dataProducts, entityFQN, @@ -52,7 +62,9 @@ const EntityRightPanel: FC = ({ entityId, showTaskHandler = true, showDataProductContainer = true, -}) => { + viewAllPermission, + customProperties, +}: EntityRightPanelProps) => { const KnowledgeArticles = entityRightPanelClassBase.getKnowLedgeArticlesWidget(); @@ -94,6 +106,30 @@ const EntityRightPanel: FC = ({ {KnowledgeArticles && ( )} + {customProperties?.extension && ( + <> +
+ + {t('label.custom-property-plural')} + + + {t('label.view-all')} + +
+ + + )} {afterSlot} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTerms/GlossaryTermsV1.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTerms/GlossaryTermsV1.component.tsx index 4d8048b83915..866499804a3c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTerms/GlossaryTermsV1.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryTerms/GlossaryTermsV1.component.tsx @@ -106,6 +106,29 @@ const GlossaryTermsV1 = ({ ); }; + const fetchGlossaryTermAssets = async () => { + if (glossaryTerm) { + try { + const encodedFqn = getEncodedFqn( + escapeESReservedCharacters(glossaryTerm.fullyQualifiedName) + ); + const res = await searchData( + '', + 1, + 0, + `(tags.tagFQN:"${encodedFqn}")`, + '', + '', + SearchIndex.ALL + ); + + setAssetCount(res.data.hits.total.value ?? 0); + } catch (error) { + setAssetCount(0); + } + } + }; + const handleAssetSave = useCallback(() => { fetchGlossaryTermAssets(); assetTabRef.current?.refreshAssets(); @@ -223,18 +246,20 @@ const GlossaryTermsV1 = ({ /> ), key: EntityTabs.CUSTOM_PROPERTIES, - children: ( - + children: glossaryTerm && ( +
+ + entityDetails={glossaryTerm} + entityType={EntityType.GLOSSARY_TERM} + handleExtensionUpdate={onExtensionUpdate} + hasEditAccess={ + !isVersionView && + (permissions.EditAll || permissions.EditCustomFields) + } + hasPermission={permissions.ViewAll} + isVersionView={isVersionView} + /> +
), }, ] @@ -256,29 +281,6 @@ const GlossaryTermsV1 = ({ onExtensionUpdate, ]); - const fetchGlossaryTermAssets = async () => { - if (glossaryTerm) { - try { - const encodedFqn = getEncodedFqn( - escapeESReservedCharacters(glossaryTerm.fullyQualifiedName) - ); - const res = await searchData( - '', - 1, - 0, - `(tags.tagFQN:"${encodedFqn}")`, - '', - '', - SearchIndex.ALL - ); - - setAssetCount(res.data.hits.total.value ?? 0); - } catch (error) { - setAssetCount(0); - } - } - }; - useEffect(() => { fetchGlossaryTermAssets(); getEntityFeedCount(); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/MlModelDetail/MlModelDetail.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/MlModelDetail/MlModelDetail.component.tsx index 099135431c79..dd605d37cddd 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/MlModelDetail/MlModelDetail.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/MlModelDetail/MlModelDetail.component.tsx @@ -419,6 +419,7 @@ const MlModelDetail: FC = ({ data-testid="entity-right-panel" flex="320px"> = ({ entityId={mlModelDetail.id} entityType={EntityType.MLMODEL} selectedTags={mlModelTags} + viewAllPermission={viewAllPermission} onTagSelectionChange={handleTagSelection} onThreadLinkSelect={handleThreadLinkSelect} /> @@ -486,13 +488,16 @@ const MlModelDetail: FC = ({ /> ), key: EntityTabs.CUSTOM_PROPERTIES, - children: ( - + children: mlModelDetail && ( +
+ + entityDetails={mlModelDetail} + entityType={EntityType.MLMODEL} + handleExtensionUpdate={onExtensionUpdate} + hasEditAccess={editCustomAttributePermission} + hasPermission={viewAllPermission} + /> +
), }, ], diff --git a/openmetadata-ui/src/main/resources/ui/src/components/PipelineDetails/PipelineDetails.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/PipelineDetails/PipelineDetails.component.tsx index 47c1ad344b8c..da6c610c0fb4 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/PipelineDetails/PipelineDetails.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/PipelineDetails/PipelineDetails.component.tsx @@ -614,6 +614,7 @@ const PipelineDetails = ({ data-testid="entity-right-panel" flex="320px"> @@ -684,13 +686,16 @@ const PipelineDetails = ({ /> ), key: EntityTabs.CUSTOM_PROPERTIES, - children: ( - + children: pipelineDetails && ( +
+ + entityDetails={pipelineDetails} + entityType={EntityType.PIPELINE} + handleExtensionUpdate={onExtensionUpdate} + hasEditAccess={editCustomAttributePermission} + hasPermission={viewAllPermission} + /> +
), }, ], diff --git a/openmetadata-ui/src/main/resources/ui/src/components/TopicDetails/TopicDetails.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/TopicDetails/TopicDetails.component.tsx index 6b4950e26e7a..6cdf7e6ad0f1 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/TopicDetails/TopicDetails.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/TopicDetails/TopicDetails.component.tsx @@ -327,6 +327,7 @@ const TopicDetails: React.FC = ({ data-testid="entity-right-panel" flex="320px"> = ({ entityId={topicDetails.id} entityType={EntityType.TOPIC} selectedTags={topicTags} + viewAllPermission={viewAllPermission} onTagSelectionChange={handleTagSelection} onThreadLinkSelect={onThreadLinkSelect} /> @@ -411,13 +413,16 @@ const TopicDetails: React.FC = ({ /> ), key: EntityTabs.CUSTOM_PROPERTIES, - children: ( - + children: topicDetails && ( +
+ + entityDetails={topicDetails} + entityType={EntityType.TOPIC} + handleExtensionUpdate={onExtensionUpdate} + hasEditAccess={editCustomAttributePermission} + hasPermission={viewAllPermission} + /> +
), }, ], @@ -477,7 +482,6 @@ const TopicDetails: React.FC = ({ { isVersionView?: boolean; entityType: T; - entityDetails?: ExtentionEntities[T]; + entityDetails: ExtentionEntities[T]; handleExtensionUpdate?: (updatedTable: ExtentionEntities[T]) => Promise; hasEditAccess: boolean; className?: string; hasPermission: boolean; + maxDataCap?: number; } diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/CustomPropertyTable/CustomPropertyTable.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/CustomPropertyTable/CustomPropertyTable.test.tsx index 9432e48b578a..13a111838e00 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/CustomPropertyTable/CustomPropertyTable.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/CustomPropertyTable/CustomPropertyTable.test.tsx @@ -19,6 +19,7 @@ import { } from '@testing-library/react'; import React from 'react'; import { EntityType } from '../../../enums/entity.enum'; +import { Table } from '../../../generated/entity/data/table'; import { getTypeByFQN } from '../../../rest/metadataTypeAPI'; import { CustomPropertyTable } from './CustomPropertyTable'; @@ -98,6 +99,34 @@ const mockProp = { entityType: EntityType.TABLE, hasEditAccess: true, hasPermission: true, + entityDetails: { + id: '0e84330a', + name: 'cypr081639', + fullyQualifiedName: 'cy-da-1705598081639', + tags: [], + version: 0.1, + updatedAt: 1705, + updatedBy: 'admin', + href: 'http://localhost:8585/api/v1/databases/', + service: { + id: '420df68ba', + type: 'databaseService', + name: 'cy-da348', + fullyQualifiedName: 'cy3348', + deleted: false, + href: 'http://localhost:8585/api/v1/services/dat83b567868ba', + }, + serviceType: 'Mysql', + default: false, + deleted: false, + columns: [], + votes: { + upVotes: 0, + downVotes: 0, + upVoters: [], + downVoters: [], + }, + } as Table, }; describe('Test CustomProperty Table Component', () => { @@ -166,4 +195,20 @@ describe('Test CustomProperty Table Component', () => { expect(noDataPlaceHolder).toBeInTheDocument(); }); + + it('Should render custom property data if custom properties list is not empty', async () => { + (getTypeByFQN as jest.Mock).mockImplementationOnce(() => + Promise.resolve({ customProperties: mockCustomProperties }) + ); + await act(async () => { + render( + + ); + }); + const tableRowTitle = await screen.findByText('xName'); + const tableRowValue = await screen.findByText('PropertyValue'); + + expect(tableRowTitle).toBeInTheDocument(); + expect(tableRowValue).toBeInTheDocument(); + }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/CustomPropertyTable/CustomPropertyTable.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/CustomPropertyTable/CustomPropertyTable.tsx index 1112eacae8ef..e41501f5bc3d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/CustomPropertyTable/CustomPropertyTable.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/CustomPropertyTable/CustomPropertyTable.tsx @@ -25,9 +25,7 @@ import { CustomProperty, Type, } from '../../../generated/entity/type'; -import { useFqn } from '../../../hooks/useFqn'; import { getTypeByFQN } from '../../../rest/metadataTypeAPI'; -import { getEntityExtentionDetailsFromEntityType } from '../../../utils/CustomProperties/CustomProperty.utils'; import { columnSorter, getEntityName } from '../../../utils/EntityUtils'; import { getChangedEntityNewValue, @@ -58,31 +56,16 @@ export const CustomPropertyTable = ({ isVersionView, hasPermission, entityDetails, + maxDataCap, }: CustomPropertyProps) => { const { t } = useTranslation(); const { getEntityPermissionByFqn } = usePermissionProvider(); - const [extentionDetails, setExtentionDetails] = - useState(); + const [entityTypeDetail, setEntityTypeDetail] = useState({} as Type); const [entityTypeDetailLoading, setEntityTypeDetailLoading] = useState(false); - const { fqn: decodedFqn } = useFqn(); - - const fetchExtentiondetails = async () => { - const response = await getEntityExtentionDetailsFromEntityType( - entityType, - decodedFqn - ); - - setExtentionDetails(response as ExtentionEntities[T]); - }; - - useEffect(() => { - fetchExtentiondetails(); - }, [decodedFqn]); const [typePermission, setPermission] = useState(); - const versionDetails = entityDetails ?? extentionDetails; const fetchTypeDetail = async () => { setEntityTypeDetailLoading(true); @@ -119,16 +102,15 @@ export const CustomPropertyTable = ({ const onExtensionUpdate = useCallback( async (updatedExtension: ExtentionEntities[T]) => { - if (!isUndefined(handleExtensionUpdate) && versionDetails) { + if (!isUndefined(handleExtensionUpdate) && entityDetails) { const updatedData = { - ...versionDetails, + ...entityDetails, extension: updatedExtension, }; await handleExtensionUpdate(updatedData); - setExtentionDetails(updatedData); } }, - [versionDetails, handleExtensionUpdate] + [entityDetails, handleExtensionUpdate] ); const extensionObject: { @@ -136,7 +118,7 @@ export const CustomPropertyTable = ({ addedKeysList?: string[]; } = useMemo(() => { if (isVersionView) { - const changeDescription = versionDetails?.changeDescription; + const changeDescription = entityDetails?.changeDescription; const extensionDiff = getDiffByFieldName( EntityField.EXTENSION, changeDescription as ChangeDescription @@ -148,19 +130,19 @@ export const CustomPropertyTable = ({ const addedFields = JSON.parse(newValues ? newValues : [])[0]; if (addedFields) { return { - extensionObject: versionDetails?.extension, + extensionObject: entityDetails?.extension, addedKeysList: Object.keys(addedFields), }; } } - if (versionDetails && extensionDiff.updated) { - return getUpdatedExtensionDiffFields(versionDetails, extensionDiff); + if (entityDetails && extensionDiff.updated) { + return getUpdatedExtensionDiffFields(entityDetails, extensionDiff); } } - return { extensionObject: versionDetails?.extension }; - }, [isVersionView, versionDetails?.extension]); + return { extensionObject: entityDetails?.extension }; + }, [isVersionView, entityDetails?.extension]); const tableColumn: ColumnsType = useMemo(() => { return [ @@ -168,7 +150,8 @@ export const CustomPropertyTable = ({ title: t('label.name'), dataIndex: 'name', key: 'name', - width: 200, + ellipsis: true, + width: '50%', render: (_, record) => getEntityName(record), sorter: columnSorter, }, @@ -190,7 +173,8 @@ export const CustomPropertyTable = ({ }, ]; }, [ - versionDetails?.extension, + entityDetails, + entityDetails?.extension, hasEditAccess, extensionObject, isVersionView, @@ -201,7 +185,7 @@ export const CustomPropertyTable = ({ if (typePermission?.ViewAll || typePermission?.ViewBasic) { fetchTypeDetail(); } - }, [typePermission]); + }, [typePermission, entityDetails?.extension]); useEffect(() => { fetchResourcePermission(entityType); @@ -221,7 +205,7 @@ export const CustomPropertyTable = ({ if ( isEmpty(entityTypeDetail.customProperties) && - isUndefined(versionDetails?.extension) + isUndefined(entityDetails?.extension) ) { return (
@@ -237,15 +221,14 @@ export const CustomPropertyTable = ({ } return isEmpty(entityTypeDetail.customProperties) && - !isUndefined(versionDetails?.extension) ? ( - + !isUndefined(entityDetails?.extension) ? ( + ) : ( { data-testid="entity-right-panel" flex="320px"> { entityId={containerData?.id ?? ''} entityType={EntityType.CONTAINER} selectedTags={tags} + viewAllPermission={viewAllPermission} onTagSelectionChange={handleTagSelection} onThreadLinkSelect={onThreadLinkSelect} /> @@ -689,13 +691,16 @@ const ContainerPage = () => { /> ), key: EntityTabs.CUSTOM_PROPERTIES, - children: ( - + children: containerData && ( +
+ + entityDetails={containerData} + entityType={EntityType.CONTAINER} + handleExtensionUpdate={handleExtensionUpdate} + hasEditAccess={editCustomAttributePermission} + hasPermission={viewAllPermission} + /> +
), }, ], diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/DatabaseDetailsPage/DatabaseDetailsPage.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/DatabaseDetailsPage/DatabaseDetailsPage.tsx index 71078e72605f..8b744f1ac48a 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/DatabaseDetailsPage/DatabaseDetailsPage.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/DatabaseDetailsPage/DatabaseDetailsPage.tsx @@ -192,7 +192,7 @@ const DatabaseDetails: FunctionComponent = () => { const getDetailsByFQN = () => { setIsDatabaseDetailsLoading(true); getDatabaseDetailsByFQN(decodedDatabaseFQN, { - fields: 'owner,tags,domain,votes', + fields: 'owner,tags,domain,votes,extension', include: Include.All, }) .then((res) => { @@ -534,6 +534,7 @@ const DatabaseDetails: FunctionComponent = () => { data-testid="entity-right-panel" flex="320px"> { entityId={database?.id ?? ''} entityType={EntityType.DATABASE} selectedTags={tags} + viewAllPermission={viewAllPermission} onTagSelectionChange={handleTagSelection} onThreadLinkSelect={onThreadLinkSelect} /> @@ -576,14 +578,17 @@ const DatabaseDetails: FunctionComponent = () => { /> ), key: EntityTabs.CUSTOM_PROPERTIES, - children: ( - + children: database && ( +
+ + entityDetails={database} + entityType={EntityType.DATABASE} + handleExtensionUpdate={settingsUpdateHandler} + hasEditAccess={editCustomAttributePermission} + hasPermission={viewAllPermission} + isVersionView={false} + /> +
), }, ], diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/DatabaseSchemaPage/DatabaseSchemaPage.component.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/DatabaseSchemaPage/DatabaseSchemaPage.component.tsx index 699a8977bb1c..1dbe2816432f 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/DatabaseSchemaPage/DatabaseSchemaPage.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/DatabaseSchemaPage/DatabaseSchemaPage.component.tsx @@ -198,7 +198,7 @@ const DatabaseSchemaPage: FunctionComponent = () => { const response = await getDatabaseSchemaDetailsByFQN( decodedDatabaseSchemaFQN, { - fields: 'owner,usageSummary,tags,domain,votes', + fields: 'owner,usageSummary,tags,domain,votes,extension', include: Include.All, } ); @@ -537,15 +537,22 @@ const DatabaseSchemaPage: FunctionComponent = () => { [databaseSchemaPermission, databaseSchema] ); - const handelExtentionUpdate = useCallback( - async (schema: DatabaseSchema) => { - await saveUpdatedDatabaseSchemaData({ - ...databaseSchema, + const handleExtensionUpdate = async (schema: DatabaseSchema) => { + await saveUpdatedDatabaseSchemaData({ + ...databaseSchema, + extension: schema.extension, + }); + setDatabaseSchema((prev) => { + if (!prev) { + return prev; + } + + return { + ...prev, extension: schema.extension, - }); - }, - [saveUpdatedDatabaseSchemaData, databaseSchema] - ); + }; + }); + }; const tabs: TabsProps['items'] = [ { @@ -583,6 +590,7 @@ const DatabaseSchemaPage: FunctionComponent = () => { data-testid="entity-right-panel" flex="320px"> { entityId={databaseSchema?.id ?? ''} entityType={EntityType.DATABASE_SCHEMA} selectedTags={tags} + viewAllPermission={viewAllPermission} onTagSelectionChange={handleTagSelection} onThreadLinkSelect={onThreadLinkSelect} /> @@ -638,15 +647,18 @@ const DatabaseSchemaPage: FunctionComponent = () => { /> ), key: EntityTabs.CUSTOM_PROPERTIES, - children: ( - + children: databaseSchema && ( +
+ + className="" + entityDetails={databaseSchema} + entityType={EntityType.DATABASE_SCHEMA} + handleExtensionUpdate={handleExtensionUpdate} + hasEditAccess={editCustomAttributePermission} + hasPermission={viewAllPermission} + isVersionView={false} + /> +
), }, ]; diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/DatabaseSchemaPage/DatabaseSchemaPage.test.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/DatabaseSchemaPage/DatabaseSchemaPage.test.tsx index 3881dfe80175..6858ff9b2077 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/DatabaseSchemaPage/DatabaseSchemaPage.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/DatabaseSchemaPage/DatabaseSchemaPage.test.tsx @@ -183,7 +183,14 @@ const mockParams = { tab: 'table', }; -const API_FIELDS = ['owner', 'usageSummary', 'tags', 'domain', 'votes']; +const API_FIELDS = [ + 'owner', + 'usageSummary', + 'tags', + 'domain', + 'votes', + 'extension', +]; jest.mock('react-router-dom', () => ({ useHistory: jest.fn().mockImplementation(() => ({ diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/Glossary/GlossaryPage/GlossaryPage.component.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/Glossary/GlossaryPage/GlossaryPage.component.tsx index 8193175f5e9b..5a51fc12571a 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/Glossary/GlossaryPage/GlossaryPage.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/Glossary/GlossaryPage/GlossaryPage.component.tsx @@ -142,7 +142,8 @@ const GlossaryPage = () => { setIsRightPanelLoading(true); try { const response = await getGlossaryTermByFQN(glossaryFqn, { - fields: 'relatedTerms,reviewers,tags,owner,children,votes,domain', + fields: + 'relatedTerms,reviewers,tags,owner,children,votes,domain,extension', }); setSelectedData(response); } catch (error) { diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/SearchIndexDetailsPage/SearchIndexDetailsPage.test.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/SearchIndexDetailsPage/SearchIndexDetailsPage.test.tsx index e7dfc0102e59..cb93a07ccbe9 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/SearchIndexDetailsPage/SearchIndexDetailsPage.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/SearchIndexDetailsPage/SearchIndexDetailsPage.test.tsx @@ -148,7 +148,7 @@ describe('SearchIndexDetailsPage component', () => { }); expect(getSearchIndexDetailsByFQN).toHaveBeenCalledWith('fqn', { - fields: 'fields,followers,tags,owner,domain,votes,dataProducts', + fields: 'fields,followers,tags,owner,domain,votes,dataProducts,extension', }); }); @@ -164,7 +164,7 @@ describe('SearchIndexDetailsPage component', () => { }); expect(getSearchIndexDetailsByFQN).toHaveBeenCalledWith('fqn', { - fields: 'fields,followers,tags,owner,domain,votes,dataProducts', + fields: 'fields,followers,tags,owner,domain,votes,dataProducts,extension', }); expect(await screen.findByText('testDataAssetsHeader')).toBeInTheDocument(); @@ -194,7 +194,7 @@ describe('SearchIndexDetailsPage component', () => { }); expect(getSearchIndexDetailsByFQN).toHaveBeenCalledWith('fqn', { - fields: 'fields,followers,tags,owner,domain,votes,dataProducts', + fields: 'fields,followers,tags,owner,domain,votes,dataProducts,extension', }); expect( diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/SearchIndexDetailsPage/SearchIndexDetailsPage.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/SearchIndexDetailsPage/SearchIndexDetailsPage.tsx index 56c53ee751ba..343a9e715c4c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/SearchIndexDetailsPage/SearchIndexDetailsPage.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/SearchIndexDetailsPage/SearchIndexDetailsPage.tsx @@ -14,7 +14,7 @@ import { Col, Row, Tabs } from 'antd'; import { AxiosError } from 'axios'; import { compare } from 'fast-json-patch'; -import { isEqual } from 'lodash'; +import { isEqual, isUndefined, omitBy } from 'lodash'; import { EntityTags } from 'Models'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; @@ -220,13 +220,12 @@ function SearchIndexDetailsPage() { history.push(getSearchIndexTabPath(decodedSearchIndexFQN, activeKey)); } }; - const saveUpdatedSearchIndexData = useCallback( (updatedData: SearchIndex) => { - if (!searchIndexDetails) { - return updatedData; - } - const jsonPatch = compare(searchIndexDetails, updatedData); + const jsonPatch = compare( + omitBy(searchIndexDetails, isUndefined), + updatedData + ); return patchSearchIndexDetails(searchIndexId, jsonPatch); }, @@ -346,72 +345,17 @@ function SearchIndexDetailsPage() { const onExtensionUpdate = useCallback( async (updatedData: SearchIndex) => { searchIndexDetails && - (await saveUpdatedSearchIndexData({ - ...searchIndexDetails, - extension: updatedData.extension, - })); + (await onSearchIndexUpdate( + { + ...searchIndexDetails, + extension: updatedData.extension, + }, + 'extension' + )); }, [saveUpdatedSearchIndexData, searchIndexDetails] ); - const fieldsTab = useMemo( - () => ( - -
-
- - -
- - - - - - ), - [ - isEdit, - searchIndexDetails, - onDescriptionEdit, - onDescriptionUpdate, - editTagsPermission, - editDescriptionPermission, - ] - ); - const tabs = useMemo(() => { const allTabs = [ { @@ -419,7 +363,57 @@ function SearchIndexDetailsPage() { ), key: EntityTabs.FIELDS, - children: fieldsTab, + children: ( + + +
+ + +
+ + + + + + ), }, { label: ( @@ -499,20 +493,22 @@ function SearchIndexDetailsPage() { /> ), key: EntityTabs.CUSTOM_PROPERTIES, - children: ( - + children: searchIndexDetails && ( +
+ + entityDetails={searchIndexDetails} + entityType={EntityType.SEARCH_INDEX} + handleExtensionUpdate={onExtensionUpdate} + hasEditAccess={editCustomAttributePermission} + hasPermission={viewAllPermission} + /> +
), }, ]; return allTabs; }, [ - fieldsTab, activeTab, searchIndexDetails, feedCount, @@ -523,6 +519,13 @@ function SearchIndexDetailsPage() { editLineagePermission, editCustomAttributePermission, viewAllPermission, + isEdit, + searchIndexDetails, + searchIndexDetails?.extension, + onDescriptionEdit, + onDescriptionUpdate, + editTagsPermission, + editDescriptionPermission, ]); const onTierUpdate = useCallback( @@ -748,7 +751,6 @@ function SearchIndexDetailsPage() {
{ ...storedProcedure, extension: updatedData.extension, })); + setStoredProcedure((prev) => { + if (!prev) { + return prev; + } + + return { + ...prev, + extension: updatedData.extension, + }; + }); }, [saveUpdatedStoredProceduresData, storedProcedure] ); @@ -559,6 +569,7 @@ const StoredProcedurePage = () => { data-testid="entity-right-panel" flex="320px"> { entityId={storedProcedure?.id ?? ''} entityType={EntityType.STORED_PROCEDURE} selectedTags={tags} + viewAllPermission={viewAllPermission} onTagSelectionChange={handleTagSelection} onThreadLinkSelect={onThreadLinkSelect} /> @@ -614,8 +626,9 @@ const StoredProcedurePage = () => { /> ), key: EntityTabs.CUSTOM_PROPERTIES, - children: ( - + entityDetails={storedProcedure} entityType={EntityType.STORED_PROCEDURE} handleExtensionUpdate={onExtensionUpdate} hasEditAccess={editCustomAttributePermission} @@ -712,7 +725,6 @@ const StoredProcedurePage = () => { {/* Entity Tabs */} DEFAULT_ENTITY_PERMISSION); const COMMON_API_FIELDS = - 'columns,followers,joins,tags,owner,dataModel,tableConstraints,viewDefinition,domain,dataProducts,votes'; + 'columns,followers,joins,tags,owner,dataModel,tableConstraints,viewDefinition,domain,dataProducts,votes,extension'; jest.mock('../../components/PermissionProvider/PermissionProvider', () => ({ usePermissionProvider: jest.fn().mockImplementation(() => ({ diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/TableDetailsPageV1/TableDetailsPageV1.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/TableDetailsPageV1/TableDetailsPageV1.tsx index de564625d018..3302454ae8b2 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/TableDetailsPageV1/TableDetailsPageV1.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/TableDetailsPageV1/TableDetailsPageV1.tsx @@ -431,10 +431,13 @@ const TableDetailsPageV1 = () => { const onExtensionUpdate = async (updatedData: Table) => { tableDetails && - (await saveUpdatedTableData({ - ...tableDetails, - extension: updatedData.extension, - })); + (await onTableUpdate( + { + ...tableDetails, + extension: updatedData.extension, + }, + 'extension' + )); }; const { @@ -545,6 +548,7 @@ const TableDetailsPageV1 = () => { ) : null } + customProperties={tableDetails} dataProducts={tableDetails?.dataProducts ?? []} domain={tableDetails?.domain} editTagPermission={editTagsPermission} @@ -552,6 +556,7 @@ const TableDetailsPageV1 = () => { entityId={tableDetails?.id ?? ''} entityType={EntityType.TABLE} selectedTags={tableTags} + viewAllPermission={viewAllPermission} onTagSelectionChange={handleTagSelection} onThreadLinkSelect={onThreadLinkSelect} /> @@ -718,13 +723,16 @@ const TableDetailsPageV1 = () => { /> ), key: EntityTabs.CUSTOM_PROPERTIES, - children: ( - + children: tableDetails && ( +
+ + entityDetails={tableDetails} + entityType={EntityType.TABLE} + handleExtensionUpdate={onExtensionUpdate} + hasEditAccess={editCustomAttributePermission} + hasPermission={viewAllPermission} + /> +
), }, ]; @@ -969,7 +977,6 @@ const TableDetailsPageV1 = () => { {/* Entity Tabs */}
{ TabSpecificField.DOMAIN, TabSpecificField.DATA_PRODUCTS, TabSpecificField.VOTES, + TabSpecificField.EXTENSION, ].join(','), }); const { id, fullyQualifiedName, serviceType } = res; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/DashboardDetailsUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/DashboardDetailsUtils.ts index 2d6020cb691b..5c8bb81365e2 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/DashboardDetailsUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/DashboardDetailsUtils.ts @@ -19,7 +19,7 @@ import { getChartById } from '../rest/chartAPI'; import { sortTagsCaseInsensitive } from './CommonUtils'; // eslint-disable-next-line max-len -export const defaultFields = `${TabSpecificField.DOMAIN},${TabSpecificField.OWNER}, ${TabSpecificField.FOLLOWERS}, ${TabSpecificField.TAGS}, ${TabSpecificField.CHARTS},${TabSpecificField.VOTES},${TabSpecificField.DATA_PRODUCTS}`; +export const defaultFields = `${TabSpecificField.DOMAIN},${TabSpecificField.OWNER}, ${TabSpecificField.FOLLOWERS}, ${TabSpecificField.TAGS}, ${TabSpecificField.CHARTS},${TabSpecificField.VOTES},${TabSpecificField.DATA_PRODUCTS},${TabSpecificField.EXTENSION}`; export const sortTagsForCharts = (charts: ChartType[]) => { return charts.map((chart) => ({ diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/DatasetDetailsUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/DatasetDetailsUtils.ts index bfff6032ce76..5a7d40404e8b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/DatasetDetailsUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/DatasetDetailsUtils.ts @@ -14,4 +14,4 @@ import { TabSpecificField } from '../enums/entity.enum'; // eslint-disable-next-line max-len -export const defaultFields = `${TabSpecificField.COLUMNS},${TabSpecificField.FOLLOWERS},${TabSpecificField.JOINS},${TabSpecificField.TAGS},${TabSpecificField.OWNER},${TabSpecificField.DATAMODEL},${TabSpecificField.TABLE_CONSTRAINTS},${TabSpecificField.VIEW_DEFINITION},${TabSpecificField.DOMAIN},${TabSpecificField.DATA_PRODUCTS},${TabSpecificField.VOTES}`; +export const defaultFields = `${TabSpecificField.COLUMNS},${TabSpecificField.FOLLOWERS},${TabSpecificField.JOINS},${TabSpecificField.TAGS},${TabSpecificField.OWNER},${TabSpecificField.DATAMODEL},${TabSpecificField.TABLE_CONSTRAINTS},${TabSpecificField.VIEW_DEFINITION},${TabSpecificField.DOMAIN},${TabSpecificField.DATA_PRODUCTS},${TabSpecificField.VOTES},${TabSpecificField.EXTENSION}`; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/MlModelDetailsUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/MlModelDetailsUtils.ts index c2c51037e870..fc6bcc6fe1af 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/MlModelDetailsUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/MlModelDetailsUtils.ts @@ -14,4 +14,4 @@ import { TabSpecificField } from '../enums/entity.enum'; // eslint-disable-next-line max-len -export const defaultFields = `${TabSpecificField.FOLLOWERS}, ${TabSpecificField.TAGS}, ${TabSpecificField.DOMAIN},${TabSpecificField.OWNER}, ${TabSpecificField.DASHBOARD},${TabSpecificField.DATA_PRODUCTS} ,${TabSpecificField.VOTES}`; +export const defaultFields = `${TabSpecificField.FOLLOWERS}, ${TabSpecificField.TAGS}, ${TabSpecificField.DOMAIN},${TabSpecificField.OWNER}, ${TabSpecificField.DASHBOARD},${TabSpecificField.DATA_PRODUCTS},${TabSpecificField.VOTES},${TabSpecificField.EXTENSION}`; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/PipelineDetailsUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/PipelineDetailsUtils.ts index f3c2df4d8543..20c5c2407631 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/PipelineDetailsUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/PipelineDetailsUtils.ts @@ -22,7 +22,7 @@ import { sortTagsCaseInsensitive } from './CommonUtils'; import { Icons } from './SvgUtils'; // eslint-disable-next-line max-len -export const defaultFields = `${TabSpecificField.FOLLOWERS}, ${TabSpecificField.TAGS}, ${TabSpecificField.OWNER},${TabSpecificField.TASKS}, ${TabSpecificField.PIPELINE_STATUS}, ${TabSpecificField.DOMAIN},${TabSpecificField.DATA_PRODUCTS},${TabSpecificField.VOTES}`; +export const defaultFields = `${TabSpecificField.FOLLOWERS}, ${TabSpecificField.TAGS}, ${TabSpecificField.OWNER},${TabSpecificField.TASKS}, ${TabSpecificField.PIPELINE_STATUS}, ${TabSpecificField.DOMAIN},${TabSpecificField.DATA_PRODUCTS},${TabSpecificField.VOTES},${TabSpecificField.EXTENSION}`; export const getTaskExecStatus = (taskName: string, tasks: TaskStatus[]) => { return tasks.find((task) => task.name === taskName)?.executionStatus || ''; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/SearchIndexUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/SearchIndexUtils.tsx index 46360d78e59f..e1fccfc6f0be 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/SearchIndexUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/SearchIndexUtils.tsx @@ -23,7 +23,7 @@ import { SearchIndexField } from '../generated/entity/data/searchIndex'; import { sortTagsCaseInsensitive } from './CommonUtils'; // eslint-disable-next-line max-len -export const defaultFields = `${TabSpecificField.FIELDS},${TabSpecificField.FOLLOWERS},${TabSpecificField.TAGS},${TabSpecificField.OWNER},${TabSpecificField.DOMAIN},${TabSpecificField.VOTES},${TabSpecificField.DATA_PRODUCTS}`; +export const defaultFields = `${TabSpecificField.FIELDS},${TabSpecificField.FOLLOWERS},${TabSpecificField.TAGS},${TabSpecificField.OWNER},${TabSpecificField.DOMAIN},${TabSpecificField.VOTES},${TabSpecificField.DATA_PRODUCTS},${TabSpecificField.EXTENSION}`; export const makeRow = (column: SearchIndexField) => { return { diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/StoredProceduresUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/StoredProceduresUtils.tsx index e81ca9d9fef4..5df30d0fc653 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/StoredProceduresUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/StoredProceduresUtils.tsx @@ -13,4 +13,4 @@ import { TabSpecificField } from '../enums/entity.enum'; // eslint-disable-next-line max-len -export const STORED_PROCEDURE_DEFAULT_FIELDS = `${TabSpecificField.OWNER}, ${TabSpecificField.FOLLOWERS},${TabSpecificField.TAGS}, ${TabSpecificField.DOMAIN},${TabSpecificField.DATA_PRODUCTS}, ${TabSpecificField.VOTES}`; +export const STORED_PROCEDURE_DEFAULT_FIELDS = `${TabSpecificField.OWNER}, ${TabSpecificField.FOLLOWERS},${TabSpecificField.TAGS}, ${TabSpecificField.DOMAIN},${TabSpecificField.DATA_PRODUCTS}, ${TabSpecificField.VOTES},${TabSpecificField.EXTENSION}`;