From c89bcb51b43d96341285be3a0c7f8181d0102649 Mon Sep 17 00:00:00 2001 From: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> Date: Fri, 14 Apr 2023 11:28:11 +0530 Subject: [PATCH] feat(ui): breadcrumb style updates (#10974) * feat(ui): breadcrumb style updates entity title styling * fix localisation * update explore card & summary panel breadcrumbs * fix breadcrumbs for container, glossary & tags * update breadcrumb styling at all the places * fix service type missing from version components * fix unit tests * fix breadcrumb styling fix icons related changes fix link related issue * fixed assets related feedbacks * add external link for assets * fix cypress * fix breadcrumbs on the glossary * fix glossary breadcrumb issue * update databaseSchema page layout * fix code smells * fix code smells * fix unit tests failing * update tag icon and fix lineage cypress * fix restore entity * fix restore entity spec * fix test id issue --- .../resources/ui/cypress/common/common.js | 2 +- .../e2e/Features/RestoreEntity.spec.js | 18 +- .../e2e/Pages/DataQualityAndProfiler.spec.js | 8 +- .../ui/cypress/e2e/Pages/Glossary.spec.js | 5 +- .../ui/cypress/e2e/Pages/myData.spec.js | 4 +- .../ui/src/assets/svg/external-link-grey.svg | 2 +- .../main/resources/ui/src/assets/svg/tag.svg | 2 +- .../ActivityThreadPanel.test.tsx | 10 - .../ActivityThreadPanelBody.test.tsx | 10 - .../AssetSelectionModal.tsx | 18 +- .../asset-selection-model.style.less | 17 ++ .../ContainerVersion.component.tsx | 1 + .../DashboardDetails.component.tsx | 1 + .../DashboardDetails.test.tsx | 10 - .../DashboardVersion.component.tsx | 1 + .../DataModelVersion.component.tsx | 1 + .../DataModels/DataModelDetails.component.tsx | 11 +- .../DatasetDetails.component.tsx | 15 +- .../DatasetDetails/DatasetDetails.test.tsx | 14 +- .../DatasetVersion.component.tsx | 3 +- .../EntityHeader/EntityHeader.component.tsx | 77 +++++++ .../EntityHeaderTitle.component.tsx | 80 +++++++ .../EntityHeaderTitle.interface.ts} | 30 +-- .../EntityHeaderTitle.test.tsx | 71 +++++++ .../EntityHeaderTitle.component.tsx | 45 ---- .../EntitySummaryPanel.component.tsx | 84 +++----- .../EntitySummaryPanel.test.tsx | 20 +- .../SummaryListItems.component.tsx | 2 +- .../TagsSummary/TagsSummary.component.tsx | 4 - .../components/Explore/explore.interface.ts | 7 + .../GlossaryHeader.component.tsx | 69 +++---- .../GlossaryHeaderButtons.component.tsx | 22 +- .../GlossaryTabs/GlossaryTabs.component.tsx | 37 +--- .../GlossaryTermsV1.component.tsx | 103 ++++++--- .../tabs/AssetsTabs.component.tsx | 42 ++-- .../MlModelDetail/MlModelDetail.component.tsx | 11 +- .../MlModelVersion.component.tsx | 1 + .../ui/src/components/MyData/MyData.test.tsx | 10 - .../PipelineDetails.component.tsx | 1 + .../PipelineDetails/PipelineDetails.test.tsx | 9 - .../PipelineVersion.component.tsx | 1 + .../ProfilerDashboard/ProfilerDashboard.tsx | 1 + .../components/TeamDetails/TeamDetailsV1.tsx | 3 +- .../TopicDetails/TopicDetails.component.tsx | 1 + .../TopicDetails/TopicDetails.test.tsx | 10 - .../TopicVersion/TopicVersion.component.tsx | 1 + .../src/components/Users/Users.component.tsx | 4 +- .../entityPageInfo/EntityPageInfo.test.tsx | 2 + .../common/entityPageInfo/EntityPageInfo.tsx | 189 +++++++++-------- .../TableDataCardTitle.component.tsx | 110 ---------- .../TableDataCardV2.test.tsx | 29 +-- .../table-data-card-v2/TableDataCardV2.tsx | 81 +++----- .../components/searched-data/SearchedData.tsx | 8 +- .../ui/src/locale/languages/en-us.json | 1 + .../ui/src/locale/languages/es-es.json | 1 + .../ui/src/locale/languages/fr-fr.json | 1 + .../ui/src/locale/languages/ja-jp.json | 1 + .../ui/src/locale/languages/pt-br.json | 1 + .../ui/src/locale/languages/zh-cn.json | 1 + .../src/pages/ContainerPage/ContainerPage.tsx | 9 +- .../DashboardDetailsPage.component.tsx | 7 - .../DatabaseSchemaPage.component.tsx | 76 ++++--- .../DatabaseSchemaPage.test.tsx | 4 + .../DatasetDetailsPage.component.tsx | 7 - .../EntityVersionPage.component.tsx | 7 - .../PipelineDetailsPage.component.tsx | 7 - .../TopicDetailsPage.component.tsx | 7 - .../src/pages/database-details/index.test.tsx | 23 +-- .../ui/src/pages/database-details/index.tsx | 58 +++--- .../ui/src/pages/service/index.test.tsx | 4 + .../resources/ui/src/pages/service/index.tsx | 195 +++++++++--------- .../resources/ui/src/pages/tags/index.tsx | 3 +- .../src/main/resources/ui/src/styles/app.less | 24 +-- .../ui/src/styles/components/button.less | 15 -- .../ui/src/styles/components/size.less | 3 + .../main/resources/ui/src/styles/fonts.less | 3 + .../resources/ui/src/styles/position.less | 5 + .../resources/ui/src/utils/EntityUtils.tsx | 165 ++++++++++++++- .../resources/ui/src/utils/ServiceUtils.tsx | 3 + .../resources/ui/src/utils/TableUtils.tsx | 10 +- 80 files changed, 1024 insertions(+), 935 deletions(-) create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityHeader/EntityHeader.component.tsx create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityHeaderTitle/EntityHeaderTitle.component.tsx rename openmetadata-ui/src/main/resources/ui/src/components/{common/table-data-card-v2/TableDataCardTitle.less => Entity/EntityHeaderTitle/EntityHeaderTitle.interface.ts} (58%) create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityHeaderTitle/EntityHeaderTitle.test.tsx delete mode 100644 openmetadata-ui/src/main/resources/ui/src/components/EntityHeaderTitle/EntityHeaderTitle.component.tsx delete mode 100644 openmetadata-ui/src/main/resources/ui/src/components/common/table-data-card-v2/TableDataCardTitle.component.tsx diff --git a/openmetadata-ui/src/main/resources/ui/cypress/common/common.js b/openmetadata-ui/src/main/resources/ui/cypress/common/common.js index fd438c04aca..4480f2bad76 100644 --- a/openmetadata-ui/src/main/resources/ui/cypress/common/common.js +++ b/openmetadata-ui/src/main/resources/ui/cypress/common/common.js @@ -323,7 +323,7 @@ export const deleteCreatedService = ( .should('be.visible') .click(); - cy.get(`[data-testid="inactive-link"]`) + cy.get(`[data-testid="entity-header-name"]`) .should('exist') .should('be.visible') .invoke('text') diff --git a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Features/RestoreEntity.spec.js b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Features/RestoreEntity.spec.js index 7792a1145de..342c6992d77 100644 --- a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Features/RestoreEntity.spec.js +++ b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Features/RestoreEntity.spec.js @@ -79,11 +79,12 @@ describe('Restore entity functionality should work properly', () => { cy.get('[data-testid="show-deleted"]').should('exist').click(); verifyResponseStatusCode('@showDeletedTables', 200); - cy.get('[data-testid="sample_data-raw_product_catalog"]') + cy.get('[data-testid="entity-header-display-name"]') + .contains('raw_product_catalog') .should('exist') .click(); - cy.get('[data-testid="inactive-link"]') + cy.get('[data-testid="entity-header-display-name"]') .should('be.visible') .contains(ENTITY_TABLE.displayName); @@ -96,18 +97,20 @@ describe('Restore entity functionality should work properly', () => { cy.get('[data-testid="show-deleted"]').should('exist').click(); verifyResponseStatusCode('@showDeletedTables', 200); - cy.get('[data-testid="sample_data-raw_product_catalog"]') + cy.get('[data-testid="entity-header-display-name"]') + .contains('raw_product_catalog') .should('exist') .click(); - cy.get('[data-testid="inactive-link"]') + cy.get('[data-testid="entity-header-display-name"]') .should('be.visible') .contains(ENTITY_TABLE.displayName) .click(); cy.get('[data-testid="deleted-badge"]').should('exist'); - cy.get('[data-testid="breadcrumb-link"]') + cy.get('[data-testid="breadcrumb"]') + .scrollIntoView() .should('be.visible') .contains(ENTITY_TABLE.schemaName) .click(); @@ -142,11 +145,12 @@ describe('Restore entity functionality should work properly', () => { cy.get('[data-testid="show-deleted"]').should('exist').click(); verifyResponseStatusCode('@showDeletedTables', 200); - cy.get('[data-testid="sample_data-raw_product_catalog"]') + cy.get('[data-testid="entity-header-display-name"]') + .contains('raw_product_catalog') .should('exist') .click(); - cy.get('[data-testid="inactive-link"]') + cy.get('[data-testid="entity-header-display-name"]') .should('be.visible') .contains(ENTITY_TABLE.displayName); diff --git a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/DataQualityAndProfiler.spec.js b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/DataQualityAndProfiler.spec.js index 6a5794769ab..c366e45ded6 100644 --- a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/DataQualityAndProfiler.spec.js +++ b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/DataQualityAndProfiler.spec.js @@ -582,7 +582,9 @@ describe('Data Quality and Profiler should work properly', () => { const { term, entity, serviceName, testCaseName } = DATA_QUALITY_SAMPLE_DATA_TABLE; visitEntityDetailsPage(term, serviceName, entity); - cy.get('[data-testid="inactive-link"]').should('be.visible').contains(term); + cy.get('[data-testid="entity-header-name"]') + .should('be.visible') + .contains(term); cy.get('[data-testid="Profiler & Data Quality"]') .should('be.visible') .click(); @@ -633,7 +635,9 @@ describe('Data Quality and Profiler should work properly', () => { ); visitEntityDetailsPage(term, serviceName, entity); verifyResponseStatusCode('@waitForPageLoad', 200); - cy.get('[data-testid="inactive-link"]').should('be.visible').contains(term); + cy.get('[data-testid="entity-header-name"]') + .should('be.visible') + .contains(term); cy.get('[data-testid="Profiler & Data Quality"]') .should('be.visible') .click(); diff --git a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/Glossary.spec.js b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/Glossary.spec.js index d879b1a52f3..7c1ccc2dc84 100644 --- a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/Glossary.spec.js +++ b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/Glossary.spec.js @@ -704,7 +704,7 @@ describe('Glossary page should work properly', () => { false ); - cy.get(`[data-testid="${entity.serviceName}-${entity.term}"]`) + cy.get('[data-testid="entity-header-display-name"]') .contains(entity.term) .should('be.visible'); }); @@ -722,7 +722,8 @@ describe('Glossary page should work properly', () => { verifyResponseStatusCode('@assetTab', 200); interceptURL('GET', '/api/v1/feed*', 'entityDetails'); - cy.get(`[data-testid="${entity.serviceName}-${entity.term}"]`) + + cy.get('[data-testid="entity-header-display-name"]') .contains(entity.term) .should('be.visible') .click(); diff --git a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/myData.spec.js b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/myData.spec.js index 0c5cfbe5f66..55c1abbeef6 100644 --- a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/myData.spec.js +++ b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Pages/myData.spec.js @@ -88,7 +88,7 @@ describe('MyData page should work', () => { entity.entityObj.serviceName, entity.entityObj.entity ); - cy.get('[data-testid="inactive-link"]') + cy.get('[data-testid="entity-header-display-name"]') .invoke('text') .then((newText) => { expect(newText).equal(text); @@ -98,7 +98,7 @@ describe('MyData page should work', () => { .contains(text) .should('be.visible') .click(); - cy.get('[data-testid="inactive-link"]') + cy.get('[data-testid="entity-header-display-name"]') .invoke('text') .then((newText) => { expect(newText).equal(text); diff --git a/openmetadata-ui/src/main/resources/ui/src/assets/svg/external-link-grey.svg b/openmetadata-ui/src/main/resources/ui/src/assets/svg/external-link-grey.svg index 7394eaa663b..1633359dc34 100644 --- a/openmetadata-ui/src/main/resources/ui/src/assets/svg/external-link-grey.svg +++ b/openmetadata-ui/src/main/resources/ui/src/assets/svg/external-link-grey.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/openmetadata-ui/src/main/resources/ui/src/assets/svg/tag.svg b/openmetadata-ui/src/main/resources/ui/src/assets/svg/tag.svg index 36a835a235a..4d3444f1b22 100644 --- a/openmetadata-ui/src/main/resources/ui/src/assets/svg/tag.svg +++ b/openmetadata-ui/src/main/resources/ui/src/assets/svg/tag.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityThreadPanel/ActivityThreadPanel.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityThreadPanel/ActivityThreadPanel.test.tsx index 3c7bc70d152..5a82ccbb649 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityThreadPanel/ActivityThreadPanel.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityThreadPanel/ActivityThreadPanel.test.tsx @@ -56,14 +56,6 @@ jest.mock('./ActivityThreadList', () => { return jest.fn().mockReturnValue(

ActivityThreadList

); }); -const mockObserve = jest.fn(); -const mockunObserve = jest.fn(); - -window.IntersectionObserver = jest.fn().mockImplementation(() => ({ - observe: mockObserve, - unobserve: mockunObserve, -})); - describe('Test ActivityThreadPanel Component', () => { beforeAll(() => { // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -100,7 +92,5 @@ describe('Test ActivityThreadPanel Component', () => { const obServerElement = await screen.findByTestId('observer-element'); expect(obServerElement).toBeInTheDocument(); - - expect(mockObserve).toHaveBeenCalled(); }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityThreadPanel/ActivityThreadPanelBody.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityThreadPanel/ActivityThreadPanelBody.test.tsx index 06403da1230..8983b8e33de 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityThreadPanel/ActivityThreadPanelBody.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityThreadPanel/ActivityThreadPanelBody.test.tsx @@ -51,14 +51,6 @@ jest.mock('./ActivityThreadList', () => { return jest.fn().mockReturnValue(

ActivityThreadList

); }); -const mockObserve = jest.fn(); -const mockunObserve = jest.fn(); - -window.IntersectionObserver = jest.fn().mockImplementation(() => ({ - observe: mockObserve, - unobserve: mockunObserve, -})); - describe('Test ActivityThreadPanelBodyBody Component', () => { beforeAll(() => { // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -89,7 +81,5 @@ describe('Test ActivityThreadPanelBodyBody Component', () => { const obServerElement = await findByTestId(container, 'observer-element'); expect(obServerElement).toBeInTheDocument(); - - expect(mockObserve).toHaveBeenCalled(); }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Assets/AssetsSelectionModal/AssetSelectionModal.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Assets/AssetsSelectionModal/AssetSelectionModal.tsx index 3829d50bbf7..1ac80b7c24d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Assets/AssetsSelectionModal/AssetSelectionModal.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Assets/AssetsSelectionModal/AssetSelectionModal.tsx @@ -55,6 +55,7 @@ export const AssetSelectionModal = ({ SearchIndex.TABLE ); const [pageNumber, setPageNumber] = useState(1); + const [totalCount, setTotalCount] = useState(0); const fetchEntities = useCallback( async ({ searchText = '', page = 1, index = activeFilter }) => { @@ -68,7 +69,7 @@ export const AssetSelectionModal = ({ queryFilter: getQueryFilterToExcludeTerm(glossaryFQN), }); const hits = res.hits.hits as SearchedDataProps['data']; - + setTotalCount(res.hits.total.value ?? 0); setItems(page === 1 ? hits : (prevItems) => [...prevItems, ...hits]); setPageNumber(page); } catch (error) { @@ -174,7 +175,10 @@ export const AssetSelectionModal = ({ const onScroll: UIEventHandler = useCallback( (e) => { - if (e.currentTarget.scrollHeight - e.currentTarget.scrollTop === 500) { + if ( + e.currentTarget.scrollHeight - e.currentTarget.scrollTop === 500 && + items.length < totalCount + ) { !isLoading && fetchEntities({ searchText: search, @@ -183,7 +187,7 @@ export const AssetSelectionModal = ({ }); } }, - [activeFilter, search] + [activeFilter, search, totalCount, items] ); return ( @@ -202,7 +206,8 @@ export const AssetSelectionModal = ({ open={open} style={{ top: 40 }} title={t('label.add-entity', { entity: t('label.asset-plural') })} - width={750}> + width={750} + onCancel={onCancel}> - {({ _index: index, _source: item }) => ( + {({ _source: item }) => ( )} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Assets/AssetsSelectionModal/asset-selection-model.style.less b/openmetadata-ui/src/main/resources/ui/src/components/Assets/AssetsSelectionModal/asset-selection-model.style.less index cd91904ec5e..16f7a069444 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Assets/AssetsSelectionModal/asset-selection-model.style.less +++ b/openmetadata-ui/src/main/resources/ui/src/components/Assets/AssetsSelectionModal/asset-selection-model.style.less @@ -16,3 +16,20 @@ .asset-selection-model-card.table-data-card-container:hover { box-shadow: 2px 3px 5px rgba(0, 0, 0, 0.13); } + +.asset-selection-model-card { + // CheckBox + .ant-checkbox-inner { + border-width: 2px; + height: 18px; + width: 18px; + border-color: #7147e8; + } + + .ant-checkbox-inner::after { + top: 48%; + left: 18.5%; + width: 6.25px; + height: 10px; + } +} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ContainerVersion/ContainerVersion.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ContainerVersion/ContainerVersion.component.tsx index 7d40d63b017..38d7b12896c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ContainerVersion/ContainerVersion.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ContainerVersion/ContainerVersion.component.tsx @@ -399,6 +399,7 @@ const ContainerVersion: React.FC = ({ entityName={currentVersionData.name ?? ''} extraInfo={getExtraInfo()} followersList={[]} + serviceType={currentVersionData.serviceType ?? ''} tags={getTags()} tier={undefined} titleLinks={breadCrumbList} 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 617d16e45f0..224698d8cba 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 @@ -662,6 +662,7 @@ const DashboardDetails = ({ ? onRemoveTier : undefined } + serviceType={dashboardDetails.serviceType ?? ''} tags={dashboardTags} tagsHandler={onTagUpdate} tier={tier} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DashboardDetails/DashboardDetails.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DashboardDetails/DashboardDetails.test.tsx index a8364bedce6..82e8b12366d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DashboardDetails/DashboardDetails.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DashboardDetails/DashboardDetails.test.tsx @@ -112,14 +112,6 @@ const DashboardDetailsProps = { onExtensionUpdate: jest.fn(), }; -const mockObserve = jest.fn(); -const mockunObserve = jest.fn(); - -window.IntersectionObserver = jest.fn().mockImplementation(() => ({ - observe: mockObserve, - unobserve: mockunObserve, -})); - jest.mock('../common/description/Description', () => { return jest.fn().mockReturnValue(

Description Component

); }); @@ -285,8 +277,6 @@ describe('Test DashboardDetails component', () => { const obServerElement = await findByTestId(container, 'observer-element'); expect(obServerElement).toBeInTheDocument(); - - expect(mockObserve).toHaveBeenCalled(); }); it('Check if tags and glossary-terms are present', async () => { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DashboardVersion/DashboardVersion.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DashboardVersion/DashboardVersion.component.tsx index e690102169c..8cc70cb6a91 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DashboardVersion/DashboardVersion.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DashboardVersion/DashboardVersion.component.tsx @@ -283,6 +283,7 @@ const DashboardVersion: FC = ({ } extraInfo={getExtraInfo()} followersList={[]} + serviceType={currentVersionData.serviceType ?? ''} tags={getTags()} tier={{} as TagLabel} titleLinks={slashedDashboardName} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataModelVersion/DataModelVersion.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataModelVersion/DataModelVersion.component.tsx index b0a1eb6aaad..ee6c2e9b652 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataModelVersion/DataModelVersion.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataModelVersion/DataModelVersion.component.tsx @@ -268,6 +268,7 @@ const DataModelVersion: FC = ({ } extraInfo={getExtraInfo()} followersList={[]} + serviceType={currentVersionData.serviceType ?? ''} tags={getTags()} tier={{} as TagLabel} titleLinks={slashedDataModelName} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataModels/DataModelDetails.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataModels/DataModelDetails.component.tsx index 0b74f68537b..1afbe4f21df 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataModels/DataModelDetails.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataModels/DataModelDetails.component.tsx @@ -44,7 +44,6 @@ import { } from 'utils/CommonUtils'; import { getEntityName } from 'utils/EntityUtils'; import { getEntityFieldThreadCounts } from 'utils/FeedUtils'; -import { serviceTypeLogo } from 'utils/ServiceUtils'; import { getTagsWithoutTier, getTierTags } from 'utils/TableUtils'; import { DataModelDetailsProps } from './DataModelDetails.interface'; import ModelTab from './ModelTab/ModelTab.component'; @@ -119,6 +118,7 @@ const DataModelDetails = ({ followers, dataModelType, isUserFollowing, + serviceType, } = useMemo(() => { return { deleted: dataModelData?.deleted, @@ -134,11 +134,11 @@ const DataModelDetails = ({ ), followers: dataModelData?.followers ?? [], dataModelType: dataModelData?.dataModelType, + serviceType: dataModelData?.serviceType, }; }, [dataModelData]); const breadcrumbTitles = useMemo(() => { - const serviceType = dataModelData?.serviceType; const service = dataModelData?.service; const serviceName = service?.name; @@ -151,12 +151,6 @@ const DataModelDetails = ({ ServiceCategory.DASHBOARD_SERVICES ) : '', - imgSrc: serviceType ? serviceTypeLogo(serviceType) : undefined, - }, - { - name: entityName, - url: '', - activeTitle: true, }, ]; }, [dataModelData, dashboardDataModelFQN, entityName]); @@ -247,6 +241,7 @@ const DataModelDetails = ({ isFollowing={isUserFollowing} isTagEditable={hasEditTagsPermission} removeTier={hasEditTierPermission ? handleRemoveTier : undefined} + serviceType={serviceType ?? ''} tags={tags} tagsHandler={handleUpdateTags} tier={tier} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DatasetDetails/DatasetDetails.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DatasetDetails/DatasetDetails.component.tsx index af4ed24c22d..c1b33ee1a9c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DatasetDetails/DatasetDetails.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DatasetDetails/DatasetDetails.component.tsx @@ -14,6 +14,9 @@ import { Card, Col, Row, Skeleton, Space, Typography } from 'antd'; import { AxiosError } from 'axios'; import classNames from 'classnames'; +// css +import QueryCount from 'components/common/QueryCount/QueryCount.component'; +import PageLayoutV1 from 'components/containers/PageLayoutV1'; import { isEqual, isNil, isUndefined } from 'lodash'; import { EntityTags, ExtraInfo } from 'Models'; import React, { @@ -82,8 +85,6 @@ import TableProfilerGraph from '../TableProfiler/TableProfilerGraph.component'; import TableProfilerV1 from '../TableProfiler/TableProfilerV1'; import TableQueries from '../TableQueries/TableQueries'; import { DatasetDetailsProps } from './DatasetDetails.interface'; -// css -import QueryCount from 'components/common/QueryCount/QueryCount.component'; import './datasetDetails.style.less'; const DatasetDetails: React.FC = ({ @@ -606,7 +607,10 @@ const DatasetDetails: React.FC = ({ ) : ( -
+ = ({ ? onRemoveTier : undefined } + serviceType={tableDetails.serviceType ?? ''} tags={tableTags} tagsHandler={onTagUpdate} tier={tier} @@ -654,7 +659,7 @@ const DatasetDetails: React.FC = ({ onThreadLinkSelect={onThreadLinkSelect} /> -
+
= ({ /> ) : null}
-
+
); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DatasetDetails/DatasetDetails.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DatasetDetails/DatasetDetails.test.tsx index 91cb56794fd..827ef810c1c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DatasetDetails/DatasetDetails.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DatasetDetails/DatasetDetails.test.tsx @@ -224,14 +224,6 @@ jest.mock('../../utils/CommonUtils', () => ({ getOwnerValue: jest.fn().mockReturnValue('Owner'), })); -const mockObserve = jest.fn(); -const mockunObserve = jest.fn(); - -window.IntersectionObserver = jest.fn().mockImplementation(() => ({ - observe: mockObserve, - unobserve: mockunObserve, -})); - jest.mock('../PermissionProvider/PermissionProvider', () => ({ usePermissionProvider: jest.fn().mockImplementation(() => ({ permissions: {}, @@ -285,6 +277,10 @@ jest.mock('../../utils/PermissionsUtils', () => ({ }, })); +jest.mock('components/containers/PageLayoutV1', () => { + return jest.fn().mockImplementation(({ children }) => children); +}); + describe('Test MyDataDetailsPage page', () => { it('Checks if the page has all the proper components rendered', async () => { const { container } = render(, { @@ -417,7 +413,5 @@ describe('Test MyDataDetailsPage page', () => { const obServerElement = await findByTestId(container, 'observer-element'); expect(obServerElement).toBeInTheDocument(); - - expect(mockObserve).toHaveBeenCalled(); }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DatasetVersion/DatasetVersion.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DatasetVersion/DatasetVersion.component.tsx index 82aa15e9b0f..96eaf57f5eb 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DatasetVersion/DatasetVersion.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DatasetVersion/DatasetVersion.component.tsx @@ -390,10 +390,11 @@ const DatasetVersion: React.FC = ({ entityName={currentVersionData.name ?? ''} extraInfo={getExtraInfo()} followersList={[]} + serviceType={currentVersionData.serviceType ?? ''} tags={getTags()} tier={tier} titleLinks={slashedTableName} - version={version} + version={Number(version)} versionHandler={backHandler} />
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityHeader/EntityHeader.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityHeader/EntityHeader.component.tsx new file mode 100644 index 00000000000..28453b5be35 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityHeader/EntityHeader.component.tsx @@ -0,0 +1,77 @@ +/* + * Copyright 2023 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 { Col, Row } from 'antd'; +import classNames from 'classnames'; +import TitleBreadcrumb from 'components/common/title-breadcrumb/title-breadcrumb.component'; +import { TitleBreadcrumbProps } from 'components/common/title-breadcrumb/title-breadcrumb.interface'; +import { EntityType } from 'enums/entity.enum'; +import React, { ReactNode } from 'react'; +import { getEntityLinkFromType, getEntityName } from 'utils/EntityUtils'; +import EntityHeaderTitle from '../EntityHeaderTitle/EntityHeaderTitle.component'; + +interface Props { + extra?: ReactNode; + breadcrumb: TitleBreadcrumbProps['titleLinks']; + entityData: { + displayName?: string; + name: string; + fullyQualifiedName?: string; + deleted?: boolean; + }; + entityType?: EntityType; + icon: ReactNode; + titleIsLink?: boolean; + openEntityInNewPage?: boolean; + gutter?: 'default' | 'large'; +} + +export const EntityHeader = ({ + breadcrumb, + entityData, + extra, + icon, + titleIsLink = false, + entityType, + openEntityInNewPage, + gutter = 'default', +}: Props) => { + return ( + + +
+ +
+ + + + {extra} +
+ ); +}; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityHeaderTitle/EntityHeaderTitle.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityHeaderTitle/EntityHeaderTitle.component.tsx new file mode 100644 index 00000000000..f4f31a53074 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityHeaderTitle/EntityHeaderTitle.component.tsx @@ -0,0 +1,80 @@ +/* + * Copyright 2023 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 { ExclamationCircleFilled } from '@ant-design/icons'; +import { Col, Row, Typography } from 'antd'; +import { ReactComponent as IconExternalLink } from 'assets/svg/external-link-grey.svg'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { Link } from 'react-router-dom'; +import { EntityHeaderTitleProps } from './EntityHeaderTitle.interface'; + +const EntityHeaderTitle = ({ + icon, + name, + displayName, + link, + openEntityInNewPage, + deleted = false, +}: EntityHeaderTitleProps) => { + const { t } = useTranslation(); + + return ( + + {icon} + +
+ + {name} + + {link ? ( + + + {displayName ?? name} + {openEntityInNewPage && ( + + )} + + + ) : ( + + {displayName ?? name} + + )} +
+ + {deleted && ( + +
+ + {t('label.deleted')} +
+ + )} +
+ ); +}; + +export default EntityHeaderTitle; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/table-data-card-v2/TableDataCardTitle.less b/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityHeaderTitle/EntityHeaderTitle.interface.ts similarity index 58% rename from openmetadata-ui/src/main/resources/ui/src/components/common/table-data-card-v2/TableDataCardTitle.less rename to openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityHeaderTitle/EntityHeaderTitle.interface.ts index 19774fe1dff..7cae0cbf9a2 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/table-data-card-v2/TableDataCardTitle.less +++ b/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityHeaderTitle/EntityHeaderTitle.interface.ts @@ -1,5 +1,5 @@ /* - * Copyright 2022 Collate. + * Copyright 2023 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 @@ -10,25 +10,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@import url('../../../styles/variables.less'); -@link-btn-color: #37352f; - -.table-data-card-title-container { - .ant-btn-link { - color: @link-btn-color; - font-weight: 600; - padding: 0px; - font-size: 16px; - } - .ant-btn-link > span { - color: @link-btn-color; - } -} - -.button-hover { - .ant-btn-link > span { - &:hover { - color: @primary-color; - } - } +export interface EntityHeaderTitleProps { + icon: React.ReactNode; + name: string; + displayName: string; + link?: string; + openEntityInNewPage?: boolean; + deleted?: boolean; } diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityHeaderTitle/EntityHeaderTitle.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityHeaderTitle/EntityHeaderTitle.test.tsx new file mode 100644 index 00000000000..6cd014377ac --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityHeaderTitle/EntityHeaderTitle.test.tsx @@ -0,0 +1,71 @@ +/* + * Copyright 2023 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 { render, screen } from '@testing-library/react'; +import React from 'react'; +import { MemoryRouter } from 'react-router-dom'; +import EntityHeaderTitle from './EntityHeaderTitle.component'; + +describe('EntityHeaderTitle', () => { + it('should render icon', () => { + render( + + ); + + expect(screen.getByText('test-icon')).toBeInTheDocument(); + }); + + it('should render name', () => { + render( + + ); + + expect(screen.getByText('test-name')).toBeInTheDocument(); + }); + + it('should render displayName', () => { + render( + + ); + + expect(screen.getByText('Test DisplayName')).toBeInTheDocument(); + }); + + it('should render link if link is provided', () => { + render( + , + { wrapper: MemoryRouter } + ); + + expect(screen.getByTestId('entity-header-display-name')).toHaveProperty( + 'href', + 'http://localhost/test-link' + ); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/EntityHeaderTitle/EntityHeaderTitle.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/EntityHeaderTitle/EntityHeaderTitle.component.tsx deleted file mode 100644 index f039441c266..00000000000 --- a/openmetadata-ui/src/main/resources/ui/src/components/EntityHeaderTitle/EntityHeaderTitle.component.tsx +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2023 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 { Col, Row, Typography } from 'antd'; -import React from 'react'; - -interface props { - icon: React.ReactNode; - name: string; - displayName: string; -} - -const EntityHeaderTitle = ({ icon, name, displayName }: props) => { - return ( - - {icon} - -
- - {name} - - - {displayName} - -
- -
- ); -}; - -export default EntityHeaderTitle; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/EntitySummaryPanel.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/EntitySummaryPanel.component.tsx index e4f0dc294c5..6c83b771b09 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/EntitySummaryPanel.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/EntitySummaryPanel.component.tsx @@ -12,19 +12,21 @@ */ import { CloseOutlined } from '@ant-design/icons'; -import { Col, Drawer, Row } from 'antd'; -import TableDataCardTitle from 'components/common/table-data-card-v2/TableDataCardTitle.component'; +import { Drawer } from 'antd'; +import { EntityHeader } from 'components/Entity/EntityHeader/EntityHeader.component'; import { EntityType } from 'enums/entity.enum'; import { Tag } from 'generated/entity/classification/tag'; import { Container } from 'generated/entity/data/container'; +import { Dashboard } from 'generated/entity/data/dashboard'; import { GlossaryTerm } from 'generated/entity/data/glossaryTerm'; +import { Table } from 'generated/entity/data/table'; import { get } from 'lodash'; -import React, { useMemo, useState } from 'react'; +import React, { useMemo } from 'react'; import { useParams } from 'react-router-dom'; -import { Dashboard } from '../../../generated/entity/data/dashboard'; +import { getEntityBreadcrumbs } from 'utils/EntityUtils'; +import { getServiceIcon } from 'utils/TableUtils'; import { Mlmodel } from '../../../generated/entity/data/mlmodel'; import { Pipeline } from '../../../generated/entity/data/pipeline'; -import { Table } from '../../../generated/entity/data/table'; import { Topic } from '../../../generated/entity/data/topic'; import ContainerSummary from './ContainerSummary/ContainerSummary.component'; import DashboardSummary from './DashboardSummary/DashboardSummary.component'; @@ -42,70 +44,44 @@ export default function EntitySummaryPanel({ handleClosePanel, }: EntitySummaryPanelProps) { const { tab } = useParams<{ tab: string }>(); - const [currentSearchIndex, setCurrentSearchIndex] = useState(); const summaryComponent = useMemo(() => { const type = get(entityDetails, 'details.entityType') ?? EntityType.TABLE; + const entity = entityDetails.details; switch (type) { case EntityType.TABLE: - setCurrentSearchIndex(EntityType.TABLE); - - return ; + return ; case EntityType.TOPIC: - setCurrentSearchIndex(EntityType.TOPIC); - - return ; + return ; case EntityType.DASHBOARD: - setCurrentSearchIndex(EntityType.DASHBOARD); - - return ( - - ); + return ; case EntityType.PIPELINE: - setCurrentSearchIndex(EntityType.PIPELINE); - - return ( - - ); + return ; case EntityType.MLMODEL: - setCurrentSearchIndex(EntityType.MLMODEL); - - return ( - - ); + return ; case EntityType.CONTAINER: - setCurrentSearchIndex(EntityType.CONTAINER); + return ; - return ( - - ); case EntityType.GLOSSARY_TERM: - setCurrentSearchIndex(EntityType.GLOSSARY); + return ; - return ( - - ); case EntityType.TAG: - setCurrentSearchIndex(EntityType.TAG); - - return ; + return ; default: return null; } }, [tab, entityDetails]); + const icon = useMemo(() => { + return getServiceIcon(entityDetails.details); + }, [entityDetails]); + return ( - - - - + } width="100%"> {summaryComponent} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/EntitySummaryPanel.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/EntitySummaryPanel.test.tsx index 6d1b1f64cb6..17e87bff408 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/EntitySummaryPanel.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/EntitySummaryPanel.test.tsx @@ -64,36 +64,30 @@ jest.mock('./MlModelSummary/MlModelSummary.component', () => )) ); -jest.mock( - 'components/common/table-data-card-v2/TableDataCardTitle.component', - () => - jest - .fn() - .mockImplementation(() => ( -
TableDataCardTitle
- )) -); - jest.mock('react-router-dom', () => ({ useParams: jest.fn().mockImplementation(() => ({ tab: 'table' })), })); +jest.mock('components/Entity/EntityHeader/EntityHeader.component', () => ({ + EntityHeader: jest.fn().mockImplementation(() =>

EntityHeader

), +})); + describe('EntitySummaryPanel component tests', () => { it('TableSummary should render for table data', async () => { render( ); - const tableDataCardTitle = screen.getByText('TableDataCardTitle'); + const entityHeader = screen.getByText('EntityHeader'); const tableSummary = screen.getByTestId('TableSummary'); const closeIcon = screen.getByTestId('summary-panel-close-icon'); - expect(tableDataCardTitle).toBeInTheDocument(); + expect(entityHeader).toBeInTheDocument(); expect(tableSummary).toBeInTheDocument(); expect(closeIcon).toBeInTheDocument(); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/SummaryList/SummaryListItems/SummaryListItems.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/SummaryList/SummaryListItems/SummaryListItems.component.tsx index 99996423863..8e7dfca818c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/SummaryList/SummaryListItems/SummaryListItems.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/SummaryList/SummaryListItems/SummaryListItems.component.tsx @@ -45,7 +45,7 @@ function SummaryListItem({ {entityDetails.title} - + {entityDetails.type && ( diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/TagsSummary/TagsSummary.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/TagsSummary/TagsSummary.component.tsx index db884f7e4fd..b36e8efb45c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/TagsSummary/TagsSummary.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/TagsSummary/TagsSummary.component.tsx @@ -18,7 +18,6 @@ import { EntityUnion } from 'components/Explore/explore.interface'; import { SourceType } from 'components/searched-data/SearchedData.interface'; import SummaryPanelSkeleton from 'components/Skeleton/SummaryPanelSkeleton/SummaryPanelSkeleton.component'; import { SearchIndex } from 'enums/search.enum'; -import { get } from 'lodash'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { searchData } from 'rest/miscAPI'; @@ -50,14 +49,11 @@ function TagsSummary({ entityDetails, isLoading }: TagsSummaryProps) { const usageItems = useMemo(() => { return selectedData.map((entity, index) => { - const searchIndex = get(entity, 'entityType'); - return ( <>
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Explore/explore.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/Explore/explore.interface.ts index 79ab8ded3f4..5c19a88369c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Explore/explore.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/explore.interface.ts @@ -127,6 +127,13 @@ export type EntityUnion = | Tag | DashboardDataModel; +export type EntityWithServices = + | Topic + | Dashboard + | Pipeline + | Mlmodel + | Container; + export interface EntityDetailsObjectInterface { details: SearchedDataProps['data'][number]['_source']; } diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryHeader/GlossaryHeader.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryHeader/GlossaryHeader.component.tsx index ee64cbfe6ff..16c2ed841ff 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryHeader/GlossaryHeader.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryHeader/GlossaryHeader.component.tsx @@ -13,16 +13,15 @@ import { Col, Row } from 'antd'; import { ReactComponent as IconFolder } from 'assets/svg/folder.svg'; import { ReactComponent as IconFlatDoc } from 'assets/svg/ic-flat-doc.svg'; -import TitleBreadcrumb from 'components/common/title-breadcrumb/title-breadcrumb.component'; import { TitleBreadcrumbProps } from 'components/common/title-breadcrumb/title-breadcrumb.interface'; -import EntityHeaderTitle from 'components/EntityHeaderTitle/EntityHeaderTitle.component'; +import { EntityHeader } from 'components/Entity/EntityHeader/EntityHeader.component'; import { OperationPermission } from 'components/PermissionProvider/PermissionProvider.interface'; import { FQN_SEPARATOR_CHAR } from 'constants/char.constants'; import { DE_ACTIVE_COLOR } from 'constants/constants'; +import { EntityType } from 'enums/entity.enum'; import { Glossary } from 'generated/entity/data/glossary'; import { GlossaryTerm } from 'generated/entity/data/glossaryTerm'; import React, { useEffect, useState } from 'react'; -import { getEntityName } from 'utils/EntityUtils'; import { getGlossaryPath } from 'utils/RouterUtils'; import GlossaryHeaderButtons from '../GlossaryHeaderButtons/GlossaryHeaderButtons.component'; @@ -33,7 +32,7 @@ export interface GlossaryHeaderProps { isGlossary: boolean; onUpdate: (data: GlossaryTerm | Glossary) => void; onDelete: (id: string) => void; - onAssetsUpdate?: () => void; + onAssetAdd?: () => void; } const GlossaryHeader = ({ @@ -42,7 +41,7 @@ const GlossaryHeader = ({ onUpdate, onDelete, isGlossary, - onAssetsUpdate, + onAssetAdd, }: GlossaryHeaderProps) => { const [breadcrumb, setBreadcrumb] = useState< TitleBreadcrumbProps['titleLinks'] @@ -88,50 +87,42 @@ const GlossaryHeader = ({ <> - - -
- -
- - - ) : ( - - ) - } - name={selectedData.name} - /> - - +
- - + } + gutter="large" + icon={ + isGlossary ? ( + + ) : ( + + ) + } + /> diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryHeaderButtons/GlossaryHeaderButtons.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryHeaderButtons/GlossaryHeaderButtons.component.tsx index dfddad13729..56da95dc6a8 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryHeaderButtons/GlossaryHeaderButtons.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Glossary/GlossaryHeaderButtons/GlossaryHeaderButtons.component.tsx @@ -16,7 +16,6 @@ import { ReactComponent as EditIcon } from 'assets/svg/edit-new.svg'; import { ReactComponent as ExportIcon } from 'assets/svg/ic-export.svg'; import { ReactComponent as ImportIcon } from 'assets/svg/ic-import.svg'; import { ReactComponent as IconDropdown } from 'assets/svg/menu.svg'; -import { AssetSelectionModal } from 'components/Assets/AssetsSelectionModal/AssetSelectionModal'; import EntityDeleteModal from 'components/Modals/EntityDeleteModal/EntityDeleteModal'; import EntityNameModal from 'components/Modals/EntityNameModal/EntityNameModal.component'; import { OperationPermission } from 'components/PermissionProvider/PermissionProvider.interface'; @@ -48,7 +47,7 @@ interface GlossaryHeaderButtonsProps { selectedData: Glossary | GlossaryTerm; permission: OperationPermission; onEntityDelete: (id: string) => void; - onAssetsUpdate?: () => void; + onAssetAdd?: () => void; onUpdate: (data: GlossaryTerm | Glossary) => void; } @@ -58,7 +57,7 @@ const GlossaryHeaderButtons = ({ selectedData, permission, onEntityDelete, - onAssetsUpdate, + onAssetAdd, onUpdate, }: GlossaryHeaderButtonsProps) => { const { t } = useTranslation(); @@ -75,7 +74,7 @@ const GlossaryHeaderButtons = ({ const history = useHistory(); const [showActions, setShowActions] = useState(false); const [isDelete, setIsDelete] = useState(false); - const [showAddAssets, setShowAddAssets] = useState(false); + const [isNameEditing, setIsNameEditing] = useState(false); const editDisplayNamePermission = useMemo(() => { @@ -133,10 +132,6 @@ const GlossaryHeaderButtons = ({ setIsDelete(false); }; - const handleAddAssetsClick = () => { - setShowAddAssets(true); - }; - const onNameSave = (obj: { name: string; displayName: string }) => { const { name, displayName } = obj; let updatedDetails = cloneDeep(selectedData); @@ -161,7 +156,7 @@ const GlossaryHeaderButtons = ({ { label: t('label.asset-plural'), key: '2', - onClick: () => handleAddAssetsClick(), + onClick: onAssetAdd, }, ]; @@ -388,14 +383,7 @@ const GlossaryHeaderButtons = ({ onOk={handleCancelGlossaryExport} /> )} - {selectedData.fullyQualifiedName && !isGlossary && ( - setShowAddAssets(false)} - onSave={onAssetsUpdate} - /> - )} + void; onUpdate: (data: GlossaryTerm | Glossary) => void; refreshGlossaryTerms: () => void; onAssetClick?: (asset?: EntityDetailsObjectInterface) => void; assetsRef: RefObject; isSummaryPanelOpen: boolean; + assetCount?: number; }; const GlossaryTabs = ({ @@ -47,10 +46,12 @@ const GlossaryTabs = ({ childGlossaryTerms, isGlossary, onUpdate, + onAddAsset, permissions, refreshGlossaryTerms, onAssetClick, assetsRef, + assetCount, isSummaryPanelOpen, }: Props) => { const { @@ -59,7 +60,6 @@ const GlossaryTabs = ({ version, } = useParams<{ glossaryName: string; tab: string; version: string }>(); const history = useHistory(); - const [assetCount, setAssetCount] = useState(0); const activeTabHandler = (tab: string) => { history.push({ @@ -69,30 +69,6 @@ const GlossaryTabs = ({ }); }; - const fetchGlossaryTermAssets = async () => { - if (glossaryFqn) { - try { - const res = await searchData( - '', - 1, - 0, - `(tags.tagFQN:"${glossaryFqn}")`, - '', - '', - myDataSearchIndex - ); - - setAssetCount(res.data.hits.total.value ?? 0); - } catch (error) { - setAssetCount(0); - } - } - }; - - useEffect(() => { - fetchGlossaryTermAssets(); - }, [glossaryFqn]); - const activeTab = useMemo(() => { return tab ?? 'overview'; }, [tab]); @@ -144,7 +120,7 @@ const GlossaryTabs = ({
{t('label.asset-plural')} - {getCountBadge(assetCount, '', activeTab === 'assets')} + {getCountBadge(assetCount ?? 0, '', activeTab === 'assets')}
), @@ -154,6 +130,7 @@ const GlossaryTabs = ({ isSummaryPanelOpen={isSummaryPanelOpen} permissions={permissions} ref={assetsRef} + onAddAsset={onAddAsset} onAssetClick={onAssetClick} /> ), diff --git a/openmetadata-ui/src/main/resources/ui/src/components/GlossaryTerms/GlossaryTermsV1.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/GlossaryTerms/GlossaryTermsV1.component.tsx index 9364e309f9b..8f68f3de997 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/GlossaryTerms/GlossaryTermsV1.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/GlossaryTerms/GlossaryTermsV1.component.tsx @@ -12,10 +12,14 @@ */ import { Col, Row } from 'antd'; +import { AssetSelectionModal } from 'components/Assets/AssetsSelectionModal/AssetSelectionModal'; import { EntityDetailsObjectInterface } from 'components/Explore/explore.interface'; import GlossaryHeader from 'components/Glossary/GlossaryHeader/GlossaryHeader.component'; import GlossaryTabs from 'components/GlossaryTabs/GlossaryTabs.component'; -import React, { useRef } from 'react'; +import { myDataSearchIndex } from 'constants/Mydata.constants'; +import React, { useEffect, useRef, useState } from 'react'; +import { useParams } from 'react-router-dom'; +import { searchData } from 'rest/miscAPI'; import { GlossaryTerm } from '../../generated/entity/data/glossaryTerm'; import { OperationPermission } from '../PermissionProvider/PermissionProvider.interface'; import { AssetsTabRef } from './tabs/AssetsTabs.component'; @@ -41,39 +45,80 @@ const GlossaryTermsV1 = ({ onAssetClick, isSummaryPanelOpen, }: Props) => { + const { glossaryName: glossaryFqn } = useParams<{ glossaryName: string }>(); const assetTabRef = useRef(null); + const [assetModalVisible, setAssetModelVisible] = useState(false); + + const [assetCount, setAssetCount] = useState(0); + + const fetchGlossaryTermAssets = async () => { + if (glossaryFqn) { + try { + const res = await searchData( + '', + 1, + 0, + `(tags.tagFQN:"${glossaryFqn}")`, + '', + '', + myDataSearchIndex + ); + + setAssetCount(res.data.hits.total.value ?? 0); + } catch (error) { + setAssetCount(0); + } + } + }; + + useEffect(() => { + fetchGlossaryTermAssets(); + }, [glossaryFqn]); + + const handleAssetSave = () => { + fetchGlossaryTermAssets(); + assetTabRef.current?.refreshAssets(); + }; return ( - - - { - if (glossaryTerm.fullyQualifiedName) { - assetTabRef.current?.refreshAssets(); - } - }} - onDelete={handleGlossaryTermDelete} - onUpdate={(data) => handleGlossaryTermUpdate(data as GlossaryTerm)} - /> - + <> + + + setAssetModelVisible(true)} + onDelete={handleGlossaryTermDelete} + onUpdate={(data) => handleGlossaryTermUpdate(data as GlossaryTerm)} + /> + - - handleGlossaryTermUpdate(data as GlossaryTerm)} + + setAssetModelVisible(true)} + onAssetClick={onAssetClick} + onUpdate={(data) => handleGlossaryTermUpdate(data as GlossaryTerm)} + /> + + + {glossaryTerm.fullyQualifiedName && ( + setAssetModelVisible(false)} + onSave={handleAssetSave} /> - - + )} + ); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/GlossaryTerms/tabs/AssetsTabs.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/GlossaryTerms/tabs/AssetsTabs.component.tsx index 36b660fd466..cb442775449 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/GlossaryTerms/tabs/AssetsTabs.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/GlossaryTerms/tabs/AssetsTabs.component.tsx @@ -31,7 +31,7 @@ import { ERROR_PLACEHOLDER_TYPE } from 'enums/common.enum'; import { EntityType } from 'enums/entity.enum'; import { SearchIndex } from 'enums/search.enum'; import { t } from 'i18next'; -import { startCase } from 'lodash'; +import { find, startCase } from 'lodash'; import React, { forwardRef, useCallback, @@ -46,6 +46,7 @@ import { getCountBadge } from '../../../utils/CommonUtils'; import ErrorPlaceHolder from '../../common/error-with-placeholder/ErrorPlaceHolder'; interface Props { + onAddAsset: () => void; permissions: OperationPermission; onAssetClick?: (asset?: EntityDetailsObjectInterface) => void; isSummaryPanelOpen: boolean; @@ -57,7 +58,10 @@ export interface AssetsTabRef { } const AssetsTabs = forwardRef( - ({ permissions, onAssetClick, isSummaryPanelOpen }: Props, ref) => { + ( + { permissions, onAssetClick, isSummaryPanelOpen, onAddAsset }: Props, + ref + ) => { const [itemCount, setItemCount] = useState>({ table: 0, pipeline: 0, @@ -98,22 +102,32 @@ const AssetsTabs = forwardRef( mlmodelResponse, containerResponse, ]) => { - setItemCount({ + const counts = { [EntityType.TOPIC]: topicResponse.data.hits.total.value, [EntityType.TABLE]: tableResponse.data.hits.total.value, [EntityType.DASHBOARD]: dashboardResponse.data.hits.total.value, [EntityType.PIPELINE]: pipelineResponse.data.hits.total.value, [EntityType.MLMODEL]: mlmodelResponse.data.hits.total.value, [EntityType.CONTAINER]: containerResponse.data.hits.total.value, - }); + }; + setItemCount(counts); - setActiveFilter( - tableResponse.data.hits.total.value - ? SearchIndex.TABLE - : topicResponse.data.hits.total.value - ? SearchIndex.TOPIC - : SearchIndex.DASHBOARD - ); + find(counts, (count, key) => { + if (count > 0) { + key; + + const option = AssetsFilterOptions.find( + (el) => el.label === key + ); + if (option) { + setActiveFilter(option.value); + } + + return true; + } + + return false; + }); } ) .catch((err) => { @@ -236,7 +250,7 @@ const AssetsTabs = forwardRef( })} {data.length ? ( <> - {data.map(({ _source, _index, _id = '' }, index) => ( + {data.map(({ _source, _id = '' }, index) => ( ))} @@ -271,7 +284,8 @@ const AssetsTabs = forwardRef( - ) : null} - {!isVersionSelected && ( - setIsAnnouncementDrawer(true)} - onRestoreEntity={onRestoreEntity} - /> - )} - - + + + {!isUndefined(version) ? ( + <> + {!isUndefined(isVersionSelected) ? ( + + {t('message.viewing-older-version')} +

+ } + trigger="hover"> + {getVersionButton(toString(version))} +
+ ) : ( + <>{getVersionButton(toString(version))} + )} + + ) : null} + {!isUndefined(isFollowing) ? ( + + ) : null} + {!isVersionSelected && ( + setIsAnnouncementDrawer(true)} + onRestoreEntity={onRestoreEntity} + /> + )} +
+ } + icon={ + serviceType && ( + + ) + } + /> - + {extraInfo.map((info, index) => ( @@ -499,9 +496,7 @@ const EntityPageInfo = ({ {(!isEditable || !isTagEditable || deleted) && ( <> {(tags.length > 0 || !isEmpty(tier)) && ( - - - + )} {tier?.tagFQN && ( setIsAnnouncementDrawer(false)} /> )} -
+
); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/table-data-card-v2/TableDataCardTitle.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/table-data-card-v2/TableDataCardTitle.component.tsx deleted file mode 100644 index 0d4925b62ba..00000000000 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/table-data-card-v2/TableDataCardTitle.component.tsx +++ /dev/null @@ -1,110 +0,0 @@ -/* - * 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, Typography } from 'antd'; -import { ReactComponent as IconExternalLink } from 'assets/svg/external-link.svg'; -import classNames from 'classnames'; -import { toString } from 'lodash'; -import React, { useMemo } from 'react'; -import { Link } from 'react-router-dom'; -import { ROUTES } from '../../../constants/constants'; -import { EntityType, FqnPart } from '../../../enums/entity.enum'; -import { SearchIndex } from '../../../enums/search.enum'; -import { - getNameFromFQN, - getPartialNameFromTableFQN, -} from '../../../utils/CommonUtils'; -import { stringToHTML } from '../../../utils/StringsUtils'; -import { getEntityLink } from '../../../utils/TableUtils'; -import './TableDataCardTitle.less'; - -interface TableDataCardTitleProps { - dataTestId?: string; - id?: string; - searchIndex: SearchIndex | EntityType; - source: { - fullyQualifiedName?: string; - displayName?: string; - name?: string; - type?: string; - }; - isPanel?: boolean; - handleLinkClick?: (e: React.MouseEvent) => void; - openEntityInNewPage?: boolean; -} - -const TableDataCardTitle = ({ - dataTestId, - id, - searchIndex, - source, - handleLinkClick, - isPanel = false, - openEntityInNewPage = false, -}: TableDataCardTitleProps) => { - const isTourRoute = location.pathname.includes(ROUTES.TOUR); - - const { testId, displayName } = useMemo( - () => ({ - testId: dataTestId - ? dataTestId - : `${getPartialNameFromTableFQN(source.fullyQualifiedName ?? '', [ - FqnPart.Service, - ])}-${source.name}`, - displayName: - source.type === 'tag' - ? toString(getNameFromFQN(source.fullyQualifiedName ?? '')) - : toString(source.displayName), - }), - [dataTestId, source] - ); - - const title = ( - - ); - - if (isTourRoute) { - return title; - } - - return ( - - - {title} - - - ); -}; - -export default TableDataCardTitle; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/table-data-card-v2/TableDataCardV2.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/table-data-card-v2/TableDataCardV2.test.tsx index 2b11abbe8d6..06291f9be53 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/table-data-card-v2/TableDataCardV2.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/table-data-card-v2/TableDataCardV2.test.tsx @@ -14,7 +14,6 @@ import { render } from '@testing-library/react'; import React from 'react'; import { MemoryRouter } from 'react-router-dom'; -import { SearchIndex } from '../../../enums/search.enum'; import TableDataCardV2 from './TableDataCardV2'; jest.mock('../../../utils/TableUtils', () => ({ @@ -42,13 +41,16 @@ jest.mock('../table-data-card/TableDataCardBody', () => { const mockHandleSummaryPanelDisplay = jest.fn(); +jest.mock('components/Entity/EntityHeader/EntityHeader.component', () => ({ + EntityHeader: jest.fn().mockImplementation(() =>

EntityHeader

), +})); + describe('Test TableDataCard Component', () => { it('Component should render', () => { - const { getByTestId } = render( + const { getByTestId, getByText } = render( { { wrapper: MemoryRouter } ); const tableDataCard = getByTestId('table-data-card'); + const entityHeader = getByText('EntityHeader'); expect(tableDataCard).toBeInTheDocument(); - }); - - it('Component should render for deleted', () => { - const { getByTestId } = render( - , - { wrapper: MemoryRouter } - ); - const deleted = getByTestId('deleted'); - - expect(deleted).toBeInTheDocument(); + expect(entityHeader).toBeInTheDocument(); }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/table-data-card-v2/TableDataCardV2.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/table-data-card-v2/TableDataCardV2.tsx index 7958cdf81b8..901eb391c83 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/table-data-card-v2/TableDataCardV2.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/table-data-card-v2/TableDataCardV2.tsx @@ -11,36 +11,31 @@ * limitations under the License. */ -import { ExclamationCircleOutlined } from '@ant-design/icons'; import { Checkbox } from 'antd'; import classNames from 'classnames'; +import { EntityHeader } from 'components/Entity/EntityHeader/EntityHeader.component'; import { isString, startCase, uniqueId } from 'lodash'; import { ExtraInfo } from 'Models'; import React, { forwardRef, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; -import { useLocation, useParams } from 'react-router-dom'; -import { getEntityId, getEntityName } from 'utils/EntityUtils'; -import AppState from '../../../AppState'; +import { useParams } from 'react-router-dom'; +import { + getEntityBreadcrumbs, + getEntityId, + getEntityName, +} from 'utils/EntityUtils'; import { FQN_SEPARATOR_CHAR } from '../../../constants/char.constants'; -import { ROUTES } from '../../../constants/constants'; import { EntityType } from '../../../enums/entity.enum'; -import { SearchIndex } from '../../../enums/search.enum'; -import { CurrentTourPageType } from '../../../enums/tour.enum'; import { OwnerType } from '../../../enums/user.enum'; import { EntityReference } from '../../../generated/entity/type'; import { getEntityPlaceHolder, getOwnerValue, } from '../../../utils/CommonUtils'; -import { - getEntityHeaderLabel, - getServiceIcon, - getUsagePercentile, -} from '../../../utils/TableUtils'; +import { getServiceIcon, getUsagePercentile } from '../../../utils/TableUtils'; import { SearchedDataProps } from '../../searched-data/SearchedData.interface'; import '../table-data-card/TableDataCard.style.css'; import TableDataCardBody from '../table-data-card/TableDataCardBody'; -import TableDataCardTitle from './TableDataCardTitle.component'; import './TableDataCardV2.less'; export interface TableDataCardPropsV2 { @@ -51,7 +46,6 @@ export interface TableDataCardPropsV2 { key: string; value: number; }[]; - searchIndex: SearchIndex | EntityType; handleSummaryPanelDisplay?: ( details: SearchedDataProps['data'][number]['_source'], entityType: string @@ -71,16 +65,14 @@ const TableDataCardV2: React.FC = forwardRef< className, source, matches, - searchIndex, handleSummaryPanelDisplay, showCheckboxes, checked, - openEntityInNewPage = false, + openEntityInNewPage, }, ref ) => { const { t } = useTranslation(); - const location = useLocation(); const { tab } = useParams<{ tab: string }>(); const otherDetails = useMemo(() => { @@ -137,21 +129,15 @@ const TableDataCardV2: React.FC = forwardRef< return _otherDetails; }, [source]); - const handleLinkClick = (e: React.MouseEvent) => { - e.stopPropagation(); - if (location.pathname.includes(ROUTES.TOUR)) { - AppState.currentTourPage = CurrentTourPageType.DATASET_PAGE; - } - }; - - const headerLabel = useMemo(() => { - return getEntityHeaderLabel(source); - }, [source]); - const serviceIcon = useMemo(() => { return getServiceIcon(source); }, [source]); + const breadcrumbs = useMemo( + () => getEntityBreadcrumbs(source, source.entityType as EntityType), + [source] + ); + return (
= forwardRef< onClick={() => { handleSummaryPanelDisplay && handleSummaryPanelDisplay(source, tab); }}> -
- {showCheckboxes && ( - - )} - {headerLabel} -
- {serviceIcon} - + + ) + } + icon={serviceIcon} + openEntityInNewPage={openEntityInNewPage} + /> - {source.deleted && ( - <> -
- - {t('label.deleted')} -
- - )} -
-
= ({ handleSummaryPanelDisplay, }) => { const highlightSearchResult = () => { - return data.map(({ _source: table, highlight, _index }, index) => { + return data.map(({ _source: table, highlight }, index) => { let tDesc = table.description ?? ''; const highLightedTexts = highlight?.description || []; @@ -65,7 +66,7 @@ const SearchedData: React.FC = ({ }); } - let name = toString(table.displayName); + let name = getEntityName(table); if (!isUndefined(highlight)) { name = highlight?.name?.join(' ') || name; } @@ -102,7 +103,6 @@ const SearchedData: React.FC = ({ handleSummaryPanelDisplay={handleSummaryPanelDisplay} id={`tabledatacard${index}`} matches={matches} - searchIndex={_index} source={{ ...table, name, description: tDesc }} />
diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json index 84998f4d406..595d8229fdb 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json @@ -282,6 +282,7 @@ "enter-property-value": "Enter Property Value", "enter-type-password": "Enter {{type}} Password", "entity-count": "{{entity}} Count", + "entity-detail-plural": "{{entity}} details", "entity-hyphen-value": "{{entity}} - {{value}}", "entity-index": "{{entity}} index", "entity-name": "{{entity}} Name", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/es-es.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/es-es.json index aaaa6be9db4..886d9b11245 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/es-es.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/es-es.json @@ -282,6 +282,7 @@ "enter-property-value": "Ingrese el valor de la propiedad", "enter-type-password": "Ingrese la contraseña de {{type}}", "entity-count": "Cantidad de {{entity}}", + "entity-detail-plural": "{{entity}} details", "entity-hyphen-value": "{{entity}} - {{value}}", "entity-index": "Índice de {{entity}}", "entity-name": "Nombre de {{entity}}", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/fr-fr.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/fr-fr.json index c3b6119084e..a369e3cd546 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/fr-fr.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/fr-fr.json @@ -282,6 +282,7 @@ "enter-property-value": "Entrer une Valeur pour la Propriété", "enter-type-password": "Enter {{type}} Password", "entity-count": "{{entity}} Count", + "entity-detail-plural": "{{entity}} details", "entity-hyphen-value": "{{entity}} - {{value}}", "entity-index": "{{entity}} index", "entity-name": "{{entity}} Name", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/ja-jp.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/ja-jp.json index 603dda878e1..e72c75fc12f 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/ja-jp.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/ja-jp.json @@ -282,6 +282,7 @@ "enter-property-value": "プロパティの値を入力", "enter-type-password": "{{type}} のパスワードを入力", "entity-count": "{{entity}}の数", + "entity-detail-plural": "{{entity}} details", "entity-hyphen-value": "{{entity}} - {{value}}", "entity-index": "{{entity}} インデックス", "entity-name": "{{entity}} 名", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-br.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-br.json index e1bae073602..a0f7c03c1eb 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-br.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/pt-br.json @@ -282,6 +282,7 @@ "enter-property-value": "Introduzir valor da propriedade", "enter-type-password": "Introduzir {{type}} da senha", "entity-count": "{{entity}} contagem", + "entity-detail-plural": "{{entity}} details", "entity-hyphen-value": "{{entity}} - {{value}}", "entity-index": "Indíce da {{entity}}", "entity-name": "Nome da {{entity}}", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/zh-cn.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/zh-cn.json index 8063bcbbcdc..677788a151e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/zh-cn.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/zh-cn.json @@ -282,6 +282,7 @@ "enter-property-value": "输入属性值", "enter-type-password": "输入 {{type}} 密码", "entity-count": "{{entity}} Count", + "entity-detail-plural": "{{entity}} details", "entity-hyphen-value": "{{entity}} - {{value}}", "entity-index": "{{entity}} index", "entity-name": "{{entity}} Name", diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/ContainerPage/ContainerPage.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/ContainerPage/ContainerPage.tsx index b0668bb14e4..7ed82a4cf3c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/ContainerPage/ContainerPage.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/ContainerPage/ContainerPage.tsx @@ -72,7 +72,6 @@ import { getContainerDetailPath } from 'utils/ContainerDetailUtils'; import { getEntityLineage, getEntityName } from 'utils/EntityUtils'; import { DEFAULT_ENTITY_PERMISSION } from 'utils/PermissionsUtils'; import { getLineageViewPath } from 'utils/RouterUtils'; -import { serviceTypeLogo } from 'utils/ServiceUtils'; import { bytesToSize } from 'utils/StringsUtils'; import { getTagsWithoutTier, getTierTags } from 'utils/TableUtils'; import { showErrorToast, showSuccessToast } from 'utils/ToastUtils'; @@ -286,7 +285,6 @@ const ContainerPage = () => { ]; const breadcrumbTitles = useMemo(() => { - const serviceType = containerData?.serviceType; const service = containerData?.service; const serviceName = service?.name; @@ -301,14 +299,8 @@ const ContainerPage = () => { url: serviceName ? getServiceDetailsPath(serviceName, ServiceCategory.STORAGE_SERVICES) : '', - imgSrc: serviceType ? serviceTypeLogo(serviceType) : undefined, }, ...parentContainerItems, - { - name: entityName, - url: '', - activeTitle: true, - }, ]; }, [containerData, containerName, entityName, parentContainers]); @@ -632,6 +624,7 @@ const ContainerPage = () => { isFollowing={isUserFollowing} isTagEditable={hasEditTagsPermission} removeTier={hasEditTierPermission ? handleRemoveTier : undefined} + serviceType={containerData?.serviceType ?? ''} tags={tags} tagsHandler={handleUpdateTags} tier={tier} diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/DashboardDetailsPage/DashboardDetailsPage.component.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/DashboardDetailsPage/DashboardDetailsPage.component.tsx index 8dff6882379..89495d1a92e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/DashboardDetailsPage/DashboardDetailsPage.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/DashboardDetailsPage/DashboardDetailsPage.component.tsx @@ -65,7 +65,6 @@ import { import { getEntityFeedLink, getEntityName } from '../../utils/EntityUtils'; import { deletePost, updateThreadData } from '../../utils/FeedUtils'; import { DEFAULT_ENTITY_PERMISSION } from '../../utils/PermissionsUtils'; -import { serviceTypeLogo } from '../../utils/ServiceUtils'; import { showErrorToast } from '../../utils/ToastUtils'; export type ChartType = { @@ -242,12 +241,6 @@ const DashboardDetailsPage = () => { ServiceCategory.DASHBOARD_SERVICES ) : '', - imgSrc: serviceType ? serviceTypeLogo(serviceType) : undefined, - }, - { - name: getEntityName(res), - url: '', - activeTitle: true, }, ]); 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 2f04c20c90e..136c8f570a9 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 @@ -33,6 +33,7 @@ import RichTextEditorPreviewer from 'components/common/rich-text-editor/RichText import TabsPane from 'components/common/TabsPane/TabsPane'; import { TitleBreadcrumbProps } from 'components/common/title-breadcrumb/title-breadcrumb.interface'; import PageContainerV1 from 'components/containers/PageContainerV1'; +import PageLayoutV1 from 'components/containers/PageLayoutV1'; import Loader from 'components/Loader/Loader'; import { usePermissionProvider } from 'components/PermissionProvider/PermissionProvider'; import { @@ -110,10 +111,7 @@ import { } from '../../utils/FeedUtils'; import { DEFAULT_ENTITY_PERMISSION } from '../../utils/PermissionsUtils'; import { getSettingPath } from '../../utils/RouterUtils'; -import { - getServiceRouteFromServiceType, - serviceTypeLogo, -} from '../../utils/ServiceUtils'; +import { getServiceRouteFromServiceType } from '../../utils/ServiceUtils'; import { getErrorText } from '../../utils/StringsUtils'; import { getEntityLink, @@ -283,7 +281,6 @@ const DatabaseSchemaPage: FunctionComponent = () => { id = '', name, service, - serviceType, database, tags, } = res; @@ -312,7 +309,6 @@ const DatabaseSchemaPage: FunctionComponent = () => { ServiceCategory.DATABASE_SERVICES ) : '', - imgSrc: serviceType ? serviceTypeLogo(serviceType) : undefined, }, { name: getPartialNameFromTableFQN( @@ -321,11 +317,6 @@ const DatabaseSchemaPage: FunctionComponent = () => { ), url: getDatabaseDetailsPath(database.fullyQualifiedName ?? ''), }, - { - name: getEntityName(res), - url: '', - activeTitle: true, - }, ]); } else { throw jsonData['api-error-messages']['unexpected-server-response']; @@ -634,7 +625,7 @@ const DatabaseSchemaPage: FunctionComponent = () => { const getSchemaTableList = () => { return ( - <> + { totalCount={tableInstanceCount} /> )} - + ); }; @@ -785,10 +776,10 @@ const DatabaseSchemaPage: FunctionComponent = () => { {databaseSchemaPermission.ViewAll || databaseSchemaPermission.ViewBasic ? ( - + {IsSchemaDetailsLoading ? ( { databaseSchemaPermission.EditAll || databaseSchemaPermission.EditTags } + serviceType={databaseSchema?.serviceType ?? ''} tags={tags} tagsHandler={onTagUpdate} tier={tier} @@ -834,27 +826,6 @@ const DatabaseSchemaPage: FunctionComponent = () => { onThreadLinkSelect={onThreadLinkSelect} /> - - - )} @@ -869,7 +840,32 @@ const DatabaseSchemaPage: FunctionComponent = () => { {activeTab === 1 && ( - {getSchemaTableList()} + + + + + + {getSchemaTableList()} + + )} {activeTab === 2 && ( @@ -914,7 +910,7 @@ const DatabaseSchemaPage: FunctionComponent = () => { /> ) : null} - + ) : ( 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 f424910f385..c4098fd7bdf 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 @@ -181,6 +181,10 @@ jest.mock('react-router-dom', () => ({ })), })); +jest.mock('components/containers/PageLayoutV1', () => { + return jest.fn().mockImplementation(({ children }) => children); +}); + describe('Tests for DatabaseSchemaPage', () => { it('Page should render properly for "Tables" tab', async () => { act(() => { diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/DatasetDetailsPage/DatasetDetailsPage.component.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/DatasetDetailsPage/DatasetDetailsPage.component.tsx index d73278799d6..9bd7eb682a3 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/DatasetDetailsPage/DatasetDetailsPage.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/DatasetDetailsPage/DatasetDetailsPage.component.tsx @@ -76,7 +76,6 @@ import { import { getEntityFeedLink, getEntityName } from '../../utils/EntityUtils'; import { deletePost, updateThreadData } from '../../utils/FeedUtils'; import { DEFAULT_ENTITY_PERMISSION } from '../../utils/PermissionsUtils'; -import { serviceTypeLogo } from '../../utils/ServiceUtils'; import { showErrorToast } from '../../utils/ToastUtils'; const DatasetDetailsPage: FunctionComponent = () => { @@ -234,7 +233,6 @@ const DatasetDetailsPage: FunctionComponent = () => { ServiceCategory.DATABASE_SERVICES ) : '', - imgSrc: serviceType ? serviceTypeLogo(serviceType) : undefined, }, { name: getPartialNameFromTableFQN(databaseFullyQualifiedName, [ @@ -251,11 +249,6 @@ const DatasetDetailsPage: FunctionComponent = () => { databaseSchemaFullyQualifiedName ), }, - { - name: getEntityName(res), - url: '', - activeTitle: true, - }, ]); addToRecentViewed({ diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/EntityVersionPage/EntityVersionPage.component.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/EntityVersionPage/EntityVersionPage.component.tsx index af5937f1ae1..5b403836fac 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/EntityVersionPage/EntityVersionPage.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/EntityVersionPage/EntityVersionPage.component.tsx @@ -206,7 +206,6 @@ const EntityVersionPage: FunctionComponent = () => { tags = [], database, service, - serviceType, databaseSchema, } = res; const serviceName = service?.name ?? ''; @@ -219,7 +218,6 @@ const EntityVersionPage: FunctionComponent = () => { ServiceCategory.DATABASE_SERVICES ) : '', - imgSrc: serviceType ? serviceTypeLogo(serviceType) : undefined, }, { name: getPartialNameFromTableFQN( @@ -237,11 +235,6 @@ const EntityVersionPage: FunctionComponent = () => { databaseSchema?.fullyQualifiedName ?? '' ), }, - { - name: getEntityName(res), - url: '', - activeTitle: true, - }, ]); getTableVersions(id) diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/PipelineDetails/PipelineDetailsPage.component.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/PipelineDetails/PipelineDetailsPage.component.tsx index b6b30e99b49..aa3aafb9877 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/PipelineDetails/PipelineDetailsPage.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/PipelineDetails/PipelineDetailsPage.component.tsx @@ -51,7 +51,6 @@ import { defaultFields, getFormattedPipelineDetails, } from '../../utils/PipelineDetailsUtils'; -import { serviceTypeLogo } from '../../utils/ServiceUtils'; import { showErrorToast } from '../../utils/ToastUtils'; const PipelineDetailsPage = () => { @@ -134,12 +133,6 @@ const PipelineDetailsPage = () => { ServiceCategory.PIPELINE_SERVICES ) : '', - imgSrc: serviceType ? serviceTypeLogo(serviceType) : undefined, - }, - { - name: getEntityName(res), - url: '', - activeTitle: true, }, ]); diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/TopicDetails/TopicDetailsPage.component.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/TopicDetails/TopicDetailsPage.component.tsx index 05a4a2eaac3..6653470d26e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/TopicDetails/TopicDetailsPage.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/TopicDetails/TopicDetailsPage.component.tsx @@ -58,7 +58,6 @@ import { import { getEntityFeedLink, getEntityName } from '../../utils/EntityUtils'; import { deletePost, updateThreadData } from '../../utils/FeedUtils'; import { DEFAULT_ENTITY_PERMISSION } from '../../utils/PermissionsUtils'; -import { serviceTypeLogo } from '../../utils/ServiceUtils'; import { showErrorToast } from '../../utils/ToastUtils'; import { getCurrentTopicTab, @@ -210,12 +209,6 @@ const TopicDetailsPage: FunctionComponent = () => { ServiceCategory.MESSAGING_SERVICES ) : '', - imgSrc: serviceType ? serviceTypeLogo(serviceType) : undefined, - }, - { - name: getEntityName(res), - url: '', - activeTitle: true, }, ]); diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/database-details/index.test.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/database-details/index.test.tsx index 8e27e81c3b4..44a3d5e3d10 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/database-details/index.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/database-details/index.test.tsx @@ -333,12 +333,13 @@ jest.mock('components/common/DeleteWidget/DeleteWidgetModal', () => {

DeleteWidgetModal component

); }); -const mockObserve = jest.fn(); -const mockunObserve = jest.fn(); -window.IntersectionObserver = jest.fn().mockImplementation(() => ({ - observe: mockObserve, - unobserve: mockunObserve, +jest.mock('components/containers/PageLayoutV1', () => { + return jest.fn().mockImplementation(({ children }) => children); +}); + +jest.mock('components/Entity/EntityHeader/EntityHeader.component', () => ({ + EntityHeader: jest.fn().mockImplementation(() =>

EntityHeader

), })); describe('Test DatabaseDetails page', () => { @@ -347,8 +348,7 @@ describe('Test DatabaseDetails page', () => { wrapper: MemoryRouter, }); - const pageContainer = await findByTestId(container, 'page-container'); - const titleBreadcrumb = await findByText(container, /TitleBreadcrumb/i); + const entityHeader = await findByText(container, 'EntityHeader'); const descriptionContainer = await findByTestId( container, 'description-container' @@ -358,8 +358,7 @@ describe('Test DatabaseDetails page', () => { 'database-databaseSchemas' ); - expect(pageContainer).toBeInTheDocument(); - expect(titleBreadcrumb).toBeInTheDocument(); + expect(entityHeader).toBeInTheDocument(); expect(descriptionContainer).toBeInTheDocument(); expect(databaseTable).toBeInTheDocument(); }); @@ -420,8 +419,7 @@ describe('Test DatabaseDetails page', () => { wrapper: MemoryRouter, }); - const pageContainer = await findByTestId(container, 'page-container'); - const titleBreadcrumb = await findByText(container, /TitleBreadcrumb/i); + const entityHeader = await findByText(container, 'EntityHeader'); const descriptionContainer = await findByTestId( container, 'description-container' @@ -431,8 +429,7 @@ describe('Test DatabaseDetails page', () => { 'database-databaseSchemas' ); - expect(pageContainer).toBeInTheDocument(); - expect(titleBreadcrumb).toBeInTheDocument(); + expect(entityHeader).toBeInTheDocument(); expect(descriptionContainer).toBeInTheDocument(); expect(databaseTable).toBeInTheDocument(); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/database-details/index.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/database-details/index.tsx index 058dbc122c1..eab2ae5b0e4 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/database-details/index.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/database-details/index.tsx @@ -23,9 +23,10 @@ import ErrorPlaceHolder from 'components/common/error-with-placeholder/ErrorPlac import NextPrevious from 'components/common/next-previous/NextPrevious'; import RichTextEditorPreviewer from 'components/common/rich-text-editor/RichTextEditorPreviewer'; import TabsPane from 'components/common/TabsPane/TabsPane'; -import TitleBreadcrumb from 'components/common/title-breadcrumb/title-breadcrumb.component'; import { TitleBreadcrumbProps } from 'components/common/title-breadcrumb/title-breadcrumb.interface'; import PageContainerV1 from 'components/containers/PageContainerV1'; +import PageLayoutV1 from 'components/containers/PageLayoutV1'; +import { EntityHeader } from 'components/Entity/EntityHeader/EntityHeader.component'; import Loader from 'components/Loader/Loader'; import { usePermissionProvider } from 'components/PermissionProvider/PermissionProvider'; import { @@ -314,12 +315,6 @@ const DatabaseDetails: FunctionComponent = () => { ServiceCategory.DATABASE_SERVICES ) : '', - imgSrc: serviceType ? serviceTypeLogo(serviceType) : undefined, - }, - { - name: getEntityName(res), - url: '', - activeTitle: true, }, ]); fetchDatabaseSchemasAndDBTModels(); @@ -658,10 +653,10 @@ const DatabaseDetails: FunctionComponent = () => { <> {databasePermission.ViewAll || databasePermission.ViewBasic ? ( - + {isDatabaseDetailsLoading ? ( { /> ) : ( <> - - - - - - + {database && ( + + } + icon={ + + } + /> + )} + {extraInfo.map((info, index) => ( @@ -809,7 +815,7 @@ const DatabaseDetails: FunctionComponent = () => { /> ) : null} - + ) : ( diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/service/index.test.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/service/index.test.tsx index 5f83e92dd7b..0ede4abd953 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/service/index.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/service/index.test.tsx @@ -271,6 +271,10 @@ jest.mock('../../utils/ToastUtils', () => ({ showErrorToast: jest.fn(), })); +jest.mock('components/containers/PageLayoutV1', () => { + return jest.fn().mockImplementation(({ children }) => children); +}); + describe('Test ServicePage Component', () => { it('Component should render', async () => { const { container } = render(, { diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/service/index.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/service/index.tsx index 43ac6ea2ff3..089e25a6727 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/service/index.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/service/index.tsx @@ -24,10 +24,11 @@ import ProfilePicture from 'components/common/ProfilePicture/ProfilePicture'; import RichTextEditorPreviewer from 'components/common/rich-text-editor/RichTextEditorPreviewer'; import TabsPane from 'components/common/TabsPane/TabsPane'; import TestConnection from 'components/common/TestConnection/TestConnection'; -import TitleBreadcrumb from 'components/common/title-breadcrumb/title-breadcrumb.component'; import { TitleBreadcrumbProps } from 'components/common/title-breadcrumb/title-breadcrumb.interface'; import PageContainerV1 from 'components/containers/PageContainerV1'; +import PageLayoutV1 from 'components/containers/PageLayoutV1'; import DataModelTable from 'components/DataModels/DataModelsTable'; +import { EntityHeader } from 'components/Entity/EntityHeader/EntityHeader.component'; import Ingestion from 'components/Ingestion/Ingestion.component'; import Loader from 'components/Loader/Loader'; import { usePermissionProvider } from 'components/PermissionProvider/PermissionProvider'; @@ -702,7 +703,7 @@ const ServicePage: FunctionComponent = () => { getServiceByFQN(serviceName, serviceFQN, 'owner') .then((resService) => { if (resService) { - const { description, serviceType } = resService; + const { description } = resService; setServiceDetails(resService); setConnectionDetails( resService.connection?.config as DashboardConnection @@ -716,12 +717,6 @@ const ServicePage: FunctionComponent = () => { getServiceRouteFromServiceType(serviceName) ), }, - { - name: getEntityName(resService), - url: '', - imgSrc: serviceType ? serviceTypeLogo(serviceType) : undefined, - activeTitle: true, - }, ]); getOtherDetails(); } else { @@ -788,6 +783,30 @@ const ServicePage: FunctionComponent = () => { } }; + const handleRemoveOwner = async () => { + const updatedData = { + ...serviceDetails, + owner: undefined, + } as ServicesUpdateRequest; + + const jsonPatch = compare(serviceDetails || {}, updatedData); + try { + const res = await updateOwnerService( + serviceName, + serviceDetails?.id ?? '', + jsonPatch + ); + setServiceDetails(res); + } catch (error) { + showErrorToast( + error as AxiosError, + t('server.entity-updating-error', { + entity: t('label.owner-lowercase'), + }) + ); + } + }; + const handleUpdateOwner = (owner: ServicesType['owner']) => { if (isUndefined(owner)) { handleRemoveOwner(); @@ -832,30 +851,6 @@ const ServicePage: FunctionComponent = () => { }); }; - const handleRemoveOwner = async () => { - const updatedData = { - ...serviceDetails, - owner: undefined, - } as ServicesUpdateRequest; - - const jsonPatch = compare(serviceDetails || {}, updatedData); - try { - const res = await updateOwnerService( - serviceName, - serviceDetails?.id ?? '', - jsonPatch - ); - setServiceDetails(res); - } catch (error) { - showErrorToast( - error as AxiosError, - t('server.entity-updating-error', { - entity: t('label.owner-lowercase'), - }) - ); - } - }; - const onDescriptionEdit = (): void => { setIsEdit(true); }; @@ -1018,69 +1013,53 @@ const ServicePage: FunctionComponent = () => { {getEntityMissingError(serviceName as string, serviceFQN)} ) : ( - <> + {servicePermission.ViewAll || servicePermission.ViewBasic ? ( - - - - - {serviceDetails?.serviceType !== - MetadataServiceType.OpenMetadata && ( - - - - )} - - history.push( - getSettingPath( - GlobalSettingsMenuCategory.SERVICES, - SERVICE_CATEGORY_TYPE[ - serviceCategory as keyof typeof SERVICE_CATEGORY_TYPE - ] - ) - ) - } - allowSoftDelete={false} - deleteMessage={getDeleteEntityMessage( - serviceName || '', - paging.total, - schemaCount, - tableCount - )} - entityId={serviceDetails?.id} - entityName={serviceDetails?.name || ''} - entityType={serviceName?.slice(0, -1)} - visible={deleteWidgetVisible} - onCancel={() => setDeleteWidgetVisible(false)} - /> - - + + {serviceDetails && ( + + + + ) + } + icon={ + + } + /> + )} {extraInfo.map((info) => ( @@ -1222,7 +1201,33 @@ const ServicePage: FunctionComponent = () => { {t('message.no-permission-to-view')}
)} - + + + history.push( + getSettingPath( + GlobalSettingsMenuCategory.SERVICES, + SERVICE_CATEGORY_TYPE[ + serviceCategory as keyof typeof SERVICE_CATEGORY_TYPE + ] + ) + ) + } + allowSoftDelete={false} + deleteMessage={getDeleteEntityMessage( + serviceName || '', + paging.total, + schemaCount, + tableCount + )} + entityId={serviceDetails?.id} + entityName={serviceDetails?.name || ''} + entityType={serviceName?.slice(0, -1)} + visible={deleteWidgetVisible} + onCancel={() => setDeleteWidgetVisible(false)} + /> + )} ); diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/tags/index.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/tags/index.tsx index e43d08b28b7..07e7aae658a 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/tags/index.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/tags/index.tsx @@ -529,8 +529,9 @@ const TagsPage = () => { useEffect(() => { /** * Fetch all classifications initially + * Do not set current if we already have currentClassification set */ - fetchClassifications(true); + fetchClassifications(!tagCategoryName); }, []); useEffect(() => { diff --git a/openmetadata-ui/src/main/resources/ui/src/styles/app.less b/openmetadata-ui/src/main/resources/ui/src/styles/app.less index 5a254ea5a51..5cf57298555 100644 --- a/openmetadata-ui/src/main/resources/ui/src/styles/app.less +++ b/openmetadata-ui/src/main/resources/ui/src/styles/app.less @@ -47,17 +47,6 @@ box-shadow: @panels-shadow-color; } -//font weight -.font-300 { - font-weight: 300; -} -.font-medium { - font-weight: 500; -} -.text-600 { - font-weight: 600; -} - // text color .text-primary { color: @primary; @@ -244,17 +233,6 @@ } } -// Font Weight -.font-normal { - font-weight: 500; -} -.font-semibold { - font-weight: 600; -} -.font-bold { - font-weight: 700; -} - .transform-180 { transform: rotate(180deg); } @@ -293,6 +271,8 @@ font-weight: 700; font-size: 18px; line-height: 22px; + color: @text-color !important; + text-decoration: none !important; } /* Opacity CSS start */ diff --git a/openmetadata-ui/src/main/resources/ui/src/styles/components/button.less b/openmetadata-ui/src/main/resources/ui/src/styles/components/button.less index 029b512a240..f545615238d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/styles/components/button.less +++ b/openmetadata-ui/src/main/resources/ui/src/styles/components/button.less @@ -34,18 +34,3 @@ button { color: initial; opacity: 0.45; } - -// CheckBox -.ant-checkbox-inner { - border-width: 2px; - height: 18px; - width: 18px; - border-color: #7147e8; -} - -.ant-checkbox-inner::after { - top: 48%; - left: 18.5%; - width: 6.25px; - height: 10px; -} diff --git a/openmetadata-ui/src/main/resources/ui/src/styles/components/size.less b/openmetadata-ui/src/main/resources/ui/src/styles/components/size.less index 225a2f78f08..e467017a479 100644 --- a/openmetadata-ui/src/main/resources/ui/src/styles/components/size.less +++ b/openmetadata-ui/src/main/resources/ui/src/styles/components/size.less @@ -140,6 +140,9 @@ } //Height +.h-inherit { + height: inherit; +} .h-3 { height: 12px; } diff --git a/openmetadata-ui/src/main/resources/ui/src/styles/fonts.less b/openmetadata-ui/src/main/resources/ui/src/styles/fonts.less index fbdc90ffd82..6b3e3359327 100644 --- a/openmetadata-ui/src/main/resources/ui/src/styles/fonts.less +++ b/openmetadata-ui/src/main/resources/ui/src/styles/fonts.less @@ -12,6 +12,9 @@ */ //font weight +.font-thin { + font-weight: 300; +} .font-normal { font-weight: 400; } diff --git a/openmetadata-ui/src/main/resources/ui/src/styles/position.less b/openmetadata-ui/src/main/resources/ui/src/styles/position.less index e6ae178781e..8b317ff6540 100644 --- a/openmetadata-ui/src/main/resources/ui/src/styles/position.less +++ b/openmetadata-ui/src/main/resources/ui/src/styles/position.less @@ -164,3 +164,8 @@ .float-right { float: right; } + +// Vertical Align +.vertical-baseline { + vertical-align: baseline; +} diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/EntityUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/EntityUtils.tsx index 4a38e92af38..2558da37cc8 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/EntityUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/EntityUtils.tsx @@ -18,12 +18,19 @@ import { LeafNodes, LineagePos, } from 'components/EntityLineage/EntityLineage.interface'; -import { EntityUnion } from 'components/Explore/explore.interface'; +import { + EntityUnion, + EntityWithServices, +} from 'components/Explore/explore.interface'; import { ResourceEntity } from 'components/PermissionProvider/PermissionProvider.interface'; +import { SearchedDataProps } from 'components/searched-data/SearchedData.interface'; import { ExplorePageTabs } from 'enums/Explore.enum'; +import { Tag } from 'generated/entity/classification/tag'; import { Container } from 'generated/entity/data/container'; import { DashboardDataModel } from 'generated/entity/data/dashboardDataModel'; +import { GlossaryTerm } from 'generated/entity/data/glossaryTerm'; import { Mlmodel } from 'generated/entity/data/mlmodel'; +import { Topic } from 'generated/entity/data/topic'; import i18next from 'i18next'; import { get, isEmpty, isNil, isUndefined, lowerCase, startCase } from 'lodash'; import { Bucket, EntityDetailUnion } from 'Models'; @@ -34,8 +41,13 @@ import { getDashboardDetailsPath, getDatabaseDetailsPath, getDatabaseSchemaDetailsPath, + getGlossaryTermDetailsPath, + getMlModelDetailsPath, + getPipelineDetailsPath, getServiceDetailsPath, getTableDetailsPath, + getTagsDetailsPath, + getTopicDetailsPath, } from '../constants/constants'; import { AssetsType, EntityType, FqnPart } from '../enums/entity.enum'; import { SearchIndex } from '../enums/search.enum'; @@ -58,6 +70,9 @@ import { getPartialNameFromTableFQN, getTableFQNFromColumnFQN, } from './CommonUtils'; +import { getContainerDetailPath } from './ContainerDetailUtils'; +import Fqn from './Fqn'; +import { getGlossaryPath } from './RouterUtils'; import { getDataTypeString, getTierFromTableTags, @@ -884,3 +899,151 @@ export const getEntityReferenceListFromEntities = < return entities.map((entity) => getEntityReferenceFromEntity(entity, type)); }; + +export const getBreadcrumbForTable = ( + entity: Table, + includeCurrent = false +) => { + const { service, database, databaseSchema } = entity; + + return [ + { + name: getEntityName(service), + url: service?.name + ? getServiceDetailsPath( + service?.name, + ServiceCategory.DATABASE_SERVICES + ) + : '', + }, + { + name: getEntityName(database), + url: getDatabaseDetailsPath(database?.fullyQualifiedName ?? ''), + }, + { + name: getEntityName(databaseSchema), + url: getDatabaseSchemaDetailsPath( + databaseSchema?.fullyQualifiedName ?? '' + ), + }, + ...(includeCurrent + ? [ + { + name: getEntityName(entity), + url: '#', + }, + ] + : []), + ]; +}; + +export const getBreadcrumbForEntitiesWithServiceOnly = ( + entity: EntityWithServices, + includeCurrent = false +) => { + const { service } = entity; + const serviceType = + service?.type === 'objectStoreService' + ? ServiceCategory.STORAGE_SERVICES + : service?.type; + + return [ + { + name: getEntityName(service), + url: service?.name + ? getServiceDetailsPath(service?.name, serviceType) + : '', + }, + ...(includeCurrent + ? [ + { + name: getEntityName(entity), + url: '#', + }, + ] + : []), + ]; +}; + +export const getEntityBreadcrumbs = ( + entity: SearchedDataProps['data'][number]['_source'], + entityType?: EntityType, + includeCurrent = false +) => { + switch (entityType) { + case EntityType.TABLE: + return getBreadcrumbForTable(entity as Table, includeCurrent); + case EntityType.GLOSSARY: + case EntityType.GLOSSARY_TERM: + // eslint-disable-next-line no-case-declarations + const glossary = (entity as GlossaryTerm).glossary; + // eslint-disable-next-line no-case-declarations + const fqnList = Fqn.split((entity as GlossaryTerm).fullyQualifiedName); + // eslint-disable-next-line no-case-declarations + const tree = fqnList.slice(1, fqnList.length - 1); + + return [ + { + name: glossary.fullyQualifiedName, + url: getGlossaryPath(glossary.fullyQualifiedName), + }, + ...tree.map((fqn, index, source) => ({ + name: fqn, + url: getGlossaryPath( + `${glossary.fullyQualifiedName}.${source + .slice(0, index + 1) + .join('.')}` + ), + })), + ]; + case EntityType.TAG: + return [ + { + name: getEntityName((entity as Tag).classification), + url: getTagsDetailsPath( + (entity as Tag).classification?.fullyQualifiedName ?? '' + ), + }, + ]; + + case EntityType.TOPIC: + case EntityType.DASHBOARD: + case EntityType.PIPELINE: + case EntityType.MLMODEL: + case EntityType.CONTAINER: + default: + return getBreadcrumbForEntitiesWithServiceOnly( + entity as Topic, + includeCurrent + ); + } +}; + +export const getEntityLinkFromType = ( + fullyQualifiedName: string, + entityType: EntityType +) => { + switch (entityType) { + case EntityType.TABLE: + return getTableDetailsPath(fullyQualifiedName); + case EntityType.GLOSSARY: + case EntityType.GLOSSARY_TERM: + return getGlossaryTermDetailsPath(fullyQualifiedName); + case EntityType.TAG: + return getTagsDetailsPath(fullyQualifiedName); + case EntityType.TOPIC: + return getTopicDetailsPath(fullyQualifiedName); + case EntityType.DASHBOARD: + return getDashboardDetailsPath(fullyQualifiedName); + case EntityType.PIPELINE: + return getPipelineDetailsPath(fullyQualifiedName); + case EntityType.MLMODEL: + return getMlModelDetailsPath(fullyQualifiedName); + case EntityType.CONTAINER: + return getContainerDetailPath(fullyQualifiedName); + case EntityType.DATABASE: + return getDatabaseDetailsPath(fullyQualifiedName); + default: + return ''; + } +}; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/ServiceUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/ServiceUtils.tsx index 23a626b2aea..4a435448508 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/ServiceUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/ServiceUtils.tsx @@ -11,6 +11,7 @@ * limitations under the License. */ +import { ReactComponent as ContainerIcon } from 'assets/svg/ic-storage.svg'; import { AxiosError } from 'axios'; import { OperationPermission, @@ -301,6 +302,8 @@ export const serviceTypeLogo = (type: string) => { logo = DATABASE_DEFAULT; } else if (serviceTypes.mlmodelServices.includes(type)) { logo = ML_MODEL_DEFAULT; + } else if (serviceTypes.storageServices.includes(type)) { + logo = ContainerIcon; } else { logo = DEFAULT_SERVICE; } diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/TableUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/TableUtils.tsx index cd7845d548a..7065afe683d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/TableUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/TableUtils.tsx @@ -14,7 +14,9 @@ import Icon from '@ant-design/icons'; import { Tooltip } from 'antd'; import { ExpandableConfig } from 'antd/lib/table/interface'; +import { ReactComponent as IconFlatFolder } from 'assets/svg/folder.svg'; import { ReactComponent as ContainerIcon } from 'assets/svg/ic-storage.svg'; +import { ReactComponent as IconTag } from 'assets/svg/tag-grey.svg'; import classNames from 'classnames'; import { SourceType } from 'components/searched-data/SearchedData.interface'; import { t } from 'i18next'; @@ -272,15 +274,15 @@ export const getEntityLink = ( export const getServiceIcon = (source: SourceType) => { if (source.entityType === EntityType.GLOSSARY_TERM) { - return ; + return ; } else if (source.entityType === EntityType.TAG) { - return ; + return ; } else { return ( service-icon ); }