diff --git a/openmetadata-ui/src/main/resources/ui/src/assets/svg/tag-grey.svg b/openmetadata-ui/src/main/resources/ui/src/assets/svg/tag-grey.svg index 4a135eec49e..ed18208ff89 100644 --- a/openmetadata-ui/src/main/resources/ui/src/assets/svg/tag-grey.svg +++ b/openmetadata-ui/src/main/resources/ui/src/assets/svg/tag-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/components/EntityInfoDrawer/EntityInfoDrawer.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/EntityInfoDrawer/EntityInfoDrawer.component.tsx index 9e78aff425c..331deada318 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/EntityInfoDrawer/EntityInfoDrawer.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/EntityInfoDrawer/EntityInfoDrawer.component.tsx @@ -12,34 +12,40 @@ */ import { CloseOutlined } from '@ant-design/icons'; -import { Divider, Drawer } from 'antd'; -import { AxiosError } from 'axios'; +import { Col, Drawer, Row, Typography } from 'antd'; import classNames from 'classnames'; -import { t } from 'i18next'; +import DashboardSummary from 'components/Explore/EntitySummaryPanel/DashboardSummary/DashboardSummary.component'; +import MlModelSummary from 'components/Explore/EntitySummaryPanel/MlModelSummary/MlModelSummary.component'; +import PipelineSummary from 'components/Explore/EntitySummaryPanel/PipelineSummary/PipelineSummary.component'; +import TableSummary from 'components/Explore/EntitySummaryPanel/TableSummary/TableSummary.component'; +import TopicSummary from 'components/Explore/EntitySummaryPanel/TopicSummary/TopicSummary.component'; +import { FQN_SEPARATOR_CHAR } from 'constants/char.constants'; +import { Mlmodel } from 'generated/entity/data/mlmodel'; import React, { useEffect, useMemo, useState } from 'react'; -import { Link } from 'react-router-dom'; +import { useTranslation } from 'react-i18next'; import { getDashboardByFqn } from 'rest/dashboardAPI'; +import { getMlModelByFQN } from 'rest/mlModelAPI'; import { getPipelineByFqn } from 'rest/pipelineAPI'; -import { getServiceById } from 'rest/serviceAPI'; import { getTableDetailsByFQN } from 'rest/tableAPI'; +import { getTopicByFqn } from 'rest/topicsAPI'; import { EntityType } from '../../enums/entity.enum'; import { Dashboard } from '../../generated/entity/data/dashboard'; import { Pipeline } from '../../generated/entity/data/pipeline'; import { Table } from '../../generated/entity/data/table'; import { Topic } from '../../generated/entity/data/topic'; import { getHeaderLabel } from '../../utils/EntityLineageUtils'; -import { getEntityOverview, getEntityTags } from '../../utils/EntityUtils'; +import { + DRAWER_NAVIGATION_OPTIONS, + getEntityTags, +} from '../../utils/EntityUtils'; import { getEncodedFqn } from '../../utils/StringsUtils'; import { getEntityIcon } from '../../utils/TableUtils'; import { showErrorToast } from '../../utils/ToastUtils'; -import RichTextEditorPreviewer from '../common/rich-text-editor/RichTextEditorPreviewer'; import { SelectedNode } from '../EntityLineage/EntityLineage.interface'; -import Loader from '../Loader/Loader'; -import TagsViewer from '../Tag/TagsViewer/tags-viewer'; import { LineageDrawerProps } from './EntityInfoDrawer.interface'; import './EntityInfoDrawer.style.less'; -type EntityData = Table | Pipeline | Dashboard | Topic; +type EntityData = Table | Pipeline | Dashboard | Topic | Mlmodel; const EntityInfoDrawer = ({ show, @@ -47,10 +53,10 @@ const EntityInfoDrawer = ({ selectedNode, isMainNode = false, }: LineageDrawerProps) => { + const { t } = useTranslation(); const [entityDetail, setEntityDetail] = useState( {} as EntityData ); - const [serviceType, setServiceType] = useState(''); const [isLoading, setIsLoading] = useState(false); @@ -67,12 +73,12 @@ const EntityInfoDrawer = ({ ]) .then((res) => { setEntityDetail(res); - setServiceType(res.serviceType ?? ''); }) - .catch((err: AxiosError) => { + .catch(() => { showErrorToast( - err, - t('server.entity-fetch-error', { entity: selectedNode.name }) + t('server.error-selected-node-name-details', { + selectedNodeName: selectedNode.name, + }) ); }) .finally(() => { @@ -83,25 +89,42 @@ const EntityInfoDrawer = ({ } case EntityType.PIPELINE: { setIsLoading(true); - getPipelineByFqn(getEncodedFqn(selectedNode.fqn), ['tags', 'owner']) + getPipelineByFqn(getEncodedFqn(selectedNode.fqn), [ + 'tags', + 'owner', + 'followers', + 'tasks', + 'tier', + ]) .then((res) => { - getServiceById('pipelineServices', res.service?.id) - .then((serviceRes) => { - setServiceType(serviceRes.serviceType ?? ''); - }) - .catch((err: AxiosError) => { - showErrorToast( - err, - t('server.entity-fetch-error', { entity: selectedNode.name }) - ); - }); setEntityDetail(res); setIsLoading(false); }) - .catch((err: AxiosError) => { + .catch(() => { showErrorToast( - err, - t('server.entity-fetch-error', { entity: selectedNode.name }) + t('server.error-selected-node-name-details', { + selectedNodeName: selectedNode.name, + }) + ); + }) + .finally(() => { + setIsLoading(false); + }); + + break; + } + + case EntityType.TOPIC: { + setIsLoading(true); + getTopicByFqn(selectedNode.fqn ?? '', ['tags', 'owner']) + .then((res) => { + setEntityDetail(res); + }) + .catch(() => { + showErrorToast( + t('server.error-selected-node-name-details', { + selectedNodeName: selectedNode.name, + }) ); }) .finally(() => { @@ -112,25 +135,20 @@ const EntityInfoDrawer = ({ } case EntityType.DASHBOARD: { setIsLoading(true); - getDashboardByFqn(getEncodedFqn(selectedNode.fqn), ['tags', 'owner']) + getDashboardByFqn(getEncodedFqn(selectedNode.fqn), [ + 'tags', + 'owner', + 'charts', + ]) .then((res) => { - getServiceById('dashboardServices', res.service?.id) - .then((serviceRes) => { - setServiceType(serviceRes.serviceType ?? ''); - }) - .catch((err: AxiosError) => { - showErrorToast( - err, - t('server.entity-fetch-error', { entity: selectedNode.name }) - ); - }); setEntityDetail(res); setIsLoading(false); }) - .catch((err: AxiosError) => { + .catch(() => { showErrorToast( - err, - t('server.entity-fetch-error', { entity: selectedNode.name }) + t('server.error-selected-node-name-details', { + selectedNodeName: selectedNode.name, + }) ); }) .finally(() => { @@ -140,16 +158,97 @@ const EntityInfoDrawer = ({ break; } + case EntityType.MLMODEL: { + setIsLoading(true); + getMlModelByFQN(getEncodedFqn(selectedNode.fqn), [ + 'tags', + 'owner', + 'dashboard', + ]) + .then((res) => { + setEntityDetail(res); + setIsLoading(false); + }) + .catch(() => { + showErrorToast( + t('server.error-selected-node-name-details', { + selectedNodeName: selectedNode.name, + }) + ); + }) + .finally(() => { + setIsLoading(false); + }); + + break; + } default: break; } }; - const entityInfo = useMemo( - () => getEntityOverview(selectedNode.type, entityDetail, serviceType), - [selectedNode.type, entityDetail, serviceType] + const tags = useMemo( + () => getEntityTags(selectedNode.type, entityDetail), + [entityDetail, selectedNode] ); + const summaryComponent = useMemo(() => { + switch (selectedNode.type) { + case EntityType.TABLE: + return ( + + ); + + case EntityType.TOPIC: + return ( + + ); + + case EntityType.DASHBOARD: + return ( + + ); + + case EntityType.PIPELINE: + return ( + + ); + + case EntityType.MLMODEL: + return ( + + ); + + default: + return null; + } + }, [entityDetail, fetchEntityDetail, tags, selectedNode]); + useEffect(() => { fetchEntityDetail(selectedNode); }, [selectedNode]); @@ -157,88 +256,45 @@ const EntityInfoDrawer = ({ return ( } + extra={ + + } getContainer={false} headerStyle={{ padding: 16 }} mask={false} + open={show} style={{ position: 'absolute' }} title={ -

- {getEntityIcon(selectedNode.type)} - {getHeaderLabel( - selectedNode.displayName ?? selectedNode.name, - selectedNode.fqn, - selectedNode.type, - isMainNode - )} -

- } - visible={show}> - {isLoading ? ( - - ) : ( - <> -
-
- {entityInfo.map((d) => { - return ( -
- {d.name && {d.name}:} - - {d.isLink ? ( - - {d.value} - - ) : ( - d.value - )} - -
- ); - })} -
-
- {entityInfo.length > 0 && } -
- {t('label.tag-plural')} -
- {getEntityTags(selectedNode.type, entityDetail).length > 0 ? ( - - ) : ( -

- {t('label.no-tags-added')} -

+ + + {'databaseSchema' in entityDetail && 'database' in entityDetail && ( + {`${entityDetail.database?.name}${FQN_SEPARATOR_CHAR}${entityDetail.databaseSchema?.name}`} + )} + + + + {getEntityIcon(selectedNode.type)} + {getHeaderLabel( + selectedNode.displayName ?? selectedNode.name, + selectedNode.fqn, + selectedNode.type, + isMainNode )} -
-
- -
- {t('label.description')} -
- {entityDetail.description?.trim() ? ( - - ) : ( -

- {t('label.no-description')} -

- )} -
-
- - )} + + + + }> + {summaryComponent}
); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/EntityInfoDrawer/EntityInfoDrawer.style.less b/openmetadata-ui/src/main/resources/ui/src/components/EntityInfoDrawer/EntityInfoDrawer.style.less index b886559c2c6..c5dbd2e9a46 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/EntityInfoDrawer/EntityInfoDrawer.style.less +++ b/openmetadata-ui/src/main/resources/ui/src/components/EntityInfoDrawer/EntityInfoDrawer.style.less @@ -20,3 +20,39 @@ height: 200px; } } + +.entity-panel-container { + .ant-drawer-body { + padding: unset; + } + + .entity-info-header-link { + &:hover { + color: @primary-color; + .ant-btn-link > span { + color: @primary-color; + } + } + + .ant-btn-link > span { + color: @text-color-secondary; + } + } + + .summary-panel-statistics-count { + color: @text-color-secondary; + font-size: 18px; + line-height: 24px; + font-weight: 700; + } + + .success { + color: @success-color; + } + .failed { + color: @failed-color; + } + .aborted { + color: @aborted-color; + } +} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/DashboardSummary/DashboardSummary.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/DashboardSummary/DashboardSummary.component.tsx index 1a610b18056..dd014512b2a 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/DashboardSummary/DashboardSummary.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/DashboardSummary/DashboardSummary.component.tsx @@ -11,28 +11,43 @@ * limitations under the License. */ -import { Col, Divider, Row, Space, Typography } from 'antd'; +import { Col, Divider, Row, Typography } from 'antd'; import { AxiosError } from 'axios'; +import classNames from 'classnames'; +import SummaryTagsDescription from 'components/common/SummaryTagsDescription/SummaryTagsDescription.component'; +import SummaryPanelSkeleton from 'components/Skeleton/SummaryPanelSkeleton/SummaryPanelSkeleton.component'; +import { ExplorePageTabs } from 'enums/Explore.enum'; +import { TagLabel } from 'generated/type/tagLabel'; import { ChartType } from 'pages/DashboardDetailsPage/DashboardDetailsPage.component'; import React, { useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Link } from 'react-router-dom'; +import { + DRAWER_NAVIGATION_OPTIONS, + getEntityOverview, +} from 'utils/EntityUtils'; +import SVGIcons from 'utils/SvgUtils'; import { SummaryEntityType } from '../../../../enums/EntitySummary.enum'; -import { SearchIndex } from '../../../../enums/search.enum'; import { Dashboard } from '../../../../generated/entity/data/dashboard'; import { fetchCharts } from '../../../../utils/DashboardDetailsUtils'; import { getFormattedEntityData } from '../../../../utils/EntitySummaryPanelUtils'; -import SVGIcons from '../../../../utils/SvgUtils'; import { showErrorToast } from '../../../../utils/ToastUtils'; -import TableDataCardTitle from '../../../common/table-data-card-v2/TableDataCardTitle.component'; import SummaryList from '../SummaryList/SummaryList.component'; import { BasicEntityInfo } from '../SummaryList/SummaryList.interface'; interface DashboardSummaryProps { entityDetails: Dashboard; + componentType?: string; + tags?: TagLabel[]; + isLoading?: boolean; } -function DashboardSummary({ entityDetails }: DashboardSummaryProps) { +function DashboardSummary({ + entityDetails, + componentType = DRAWER_NAVIGATION_OPTIONS.explore, + tags, + isLoading, +}: DashboardSummaryProps) { const { t } = useTranslation(); const [charts, setCharts] = useState(); @@ -56,6 +71,11 @@ function DashboardSummary({ entityDetails }: DashboardSummaryProps) { } }; + const entityInfo = useMemo( + () => getEntityOverview(ExplorePageTabs.DASHBOARDS, entityDetails), + [entityDetails] + ); + useEffect(() => { fetchChartsDetails(); }, [entityDetails]); @@ -65,63 +85,96 @@ function DashboardSummary({ entityDetails }: DashboardSummaryProps) { [charts] ); + const isExplore = useMemo( + () => componentType === DRAWER_NAVIGATION_OPTIONS.explore, + [componentType] + ); + return ( - <> - - - - - - - - {`${t('label.dashboard')} ${t('label.url-uppercase')}`} - - - {entityDetails.dashboardUrl ? ( - - - - {entityDetails.name} - - - - - ) : ( - '-' - )} - - - - - - - - - {t('label.chart-plural')} - - - - - - - + + <> + + + + {entityInfo.map((info) => { + const isOwner = info.name === t('label.owner'); + + return info.visible?.includes(componentType) ? ( + + + {!isOwner ? ( + + + {info.name} + + + ) : null} + + {info.isLink ? ( + + + {info.value} + + {info.isExternal ? ( + + ) : null} + + ) : ( + + {info.value} + + )} + + + + ) : null; + })} + + + + + + {!isExplore ? ( + <> + + + + ) : null} + + + + + {t('label.chart-plural')} + + + + + + + + ); } diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/DashboardSummary/DashboardSummary.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/DashboardSummary/DashboardSummary.test.tsx index 2901dd5dc28..33849f3ce58 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/DashboardSummary/DashboardSummary.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/DashboardSummary/DashboardSummary.test.tsx @@ -14,22 +14,13 @@ import { act, render, screen } from '@testing-library/react'; import React from 'react'; import { MemoryRouter } from 'react-router-dom'; +import { DRAWER_NAVIGATION_OPTIONS } from 'utils/EntityUtils'; import { mockDashboardEntityDetails, mockFetchChartsResponse, } from '../mocks/DashboardSummary.mock'; import DashboardSummary from './DashboardSummary.component'; -jest.mock( - '../../../common/table-data-card-v2/TableDataCardTitle.component', - () => - jest - .fn() - .mockImplementation(() => ( -
TableDataCardTitle
- )) -); - jest.mock('../SummaryList/SummaryList.component', () => jest .fn() @@ -48,19 +39,68 @@ describe('DashboardSummary component tests', () => { }); }); - const dashboardTitle = screen.getByTestId('TableDataCardTitle'); - const dashboardUrlLabel = screen.getByTestId('dashboard-url-label'); - const dashboardUrlValue = screen.getByTestId('dashboard-link-name'); + const dashboardUrlLabel = screen.getByText( + 'label.dashboard label.url-uppercase' + ); + const dashboardUrlValue = screen.getByTestId('dashboard-url-value'); + const dashboardLinkName = screen.getByTestId('dashboard-link-name'); const chartsHeader = screen.getByTestId('charts-header'); const summaryList = screen.getByTestId('SummaryList'); - expect(dashboardTitle).toBeInTheDocument(); + expect(dashboardLinkName).toBeInTheDocument(); expect(dashboardUrlLabel).toBeInTheDocument(); expect(dashboardUrlValue).toContainHTML(mockDashboardEntityDetails.name); expect(chartsHeader).toBeInTheDocument(); expect(summaryList).toBeInTheDocument(); }); + it('Component should render properly, when loaded in the Lineage page.', async () => { + const labels = ['label.service-label', 'label.tier-label']; + + await act(async () => { + const { debug } = render( + , + { + wrapper: MemoryRouter, + } + ); + + debug(); + }); + + const dashboardUrlLabel = screen.getByText( + 'label.dashboard label.url-uppercase' + ); + const ownerLabel = screen.queryByTestId('label.owner-label'); + + const dashboardUrl = screen.getAllByTestId('dashboard-url-value'); + + const tags = screen.getByText('label.tag-plural'); + const description = screen.getByText('label.description'); + const noDataFound = screen.getByText('label.no-data-found'); + const dashboardLink = screen.getAllByTestId('dashboard-link-name'); + const dashboardValue = screen.getAllByTestId('dashboard-url-value'); + + labels.forEach((label) => + expect(screen.getByTestId(label)).toBeInTheDocument() + ); + + expect(ownerLabel).not.toBeInTheDocument(); + + expect(dashboardUrl[0]).toBeInTheDocument(); + expect(dashboardLink[0]).toBeInTheDocument(); + expect(dashboardValue[0]).toBeInTheDocument(); + + expect(tags).toBeInTheDocument(); + expect(description).toBeInTheDocument(); + expect(noDataFound).toBeInTheDocument(); + + expect(dashboardUrlLabel).toBeInTheDocument(); + }); + it('If the dashboard url is not present in dashboard details, "-" should be displayed as dashboard url value', async () => { await act(async () => { render( 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 ddb85db6dbc..25434e20718 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,8 +12,10 @@ */ import { CloseOutlined } from '@ant-design/icons'; -import classNames from 'classnames'; -import React, { useMemo } from 'react'; +import { Col, Drawer, Row } from 'antd'; +import TableDataCardTitle from 'components/common/table-data-card-v2/TableDataCardTitle.component'; +import { EntityType } from 'enums/entity.enum'; +import React, { useMemo, useState } from 'react'; import { useParams } from 'react-router-dom'; import { ExplorePageTabs } from '../../../enums/Explore.enum'; import { Dashboard } from '../../../generated/entity/data/dashboard'; @@ -34,16 +36,23 @@ export default function EntitySummaryPanel({ handleClosePanel, }: EntitySummaryPanelProps) { const { tab } = useParams<{ tab: string }>(); + const [currentSearchIndex, setCurrentSearchIndex] = useState(); const summaryComponent = useMemo(() => { switch (entityDetails.entityType) { case ExplorePageTabs.TABLES: + setCurrentSearchIndex(EntityType.TABLE); + return ; case ExplorePageTabs.TOPICS: + setCurrentSearchIndex(EntityType.TOPIC); + return ; case ExplorePageTabs.DASHBOARDS: + setCurrentSearchIndex(EntityType.DASHBOARD); + return ( ); case ExplorePageTabs.MLMODELS: + setCurrentSearchIndex(EntityType.MLMODEL); + return ( ); @@ -66,13 +79,34 @@ export default function EntitySummaryPanel({ }, [tab, entityDetails]); return ( -
+ + } + getContainer={false} + headerStyle={{ padding: 16 }} + mask={false} + title={ + + + + + + } + width="100%"> {summaryComponent} - -
+ ); } diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/EntitySummaryPanel.style.less b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/EntitySummaryPanel.style.less index b6a0ce9b818..f2ab77d9f6c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/EntitySummaryPanel.style.less +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/EntitySummaryPanel.style.less @@ -33,31 +33,25 @@ -ms-overflow-style: none; scrollbar-width: none; - .ant-typography { - color: #37352f; + .ant-drawer-content-wrapper { + box-shadow: -2px 2px 4px rgba(0, 0, 0, 0.12); + } + .ant-drawer-body { + padding: unset; } .summary-panel-statistics-count { - color: #37352f; + color: @text-color-secondary; font-size: 18px; line-height: 24px; font-weight: 700; } - .text-gray { - color: @label-color; - } - - .section-header { - font-size: 16px; - color: @section-header-color; - } - .success { color: @successColor; } .failed { - color: @failedColor; + color: @failed-color; } .aborted { color: @abortedColor; 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 5f7eddb0694..8545c9c8ad2 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,6 +64,16 @@ 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' })), })); @@ -80,9 +90,11 @@ describe('EntitySummaryPanel component tests', () => { /> ); + const tableDataCardTitle = screen.getByText('TableDataCardTitle'); const tableSummary = screen.getByTestId('TableSummary'); const closeIcon = screen.getByTestId('summary-panel-close-icon'); + expect(tableDataCardTitle).toBeInTheDocument(); expect(tableSummary).toBeInTheDocument(); expect(closeIcon).toBeInTheDocument(); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/MlModelSummary/MlModelSummary.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/MlModelSummary/MlModelSummary.component.tsx index f9580094cbe..e1f502f88b9 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/MlModelSummary/MlModelSummary.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/MlModelSummary/MlModelSummary.component.tsx @@ -12,50 +12,42 @@ */ import { Col, Divider, Row, Typography } from 'antd'; -import { startCase } from 'lodash'; -import React, { ReactNode, useMemo } from 'react'; +import classNames from 'classnames'; +import SummaryTagsDescription from 'components/common/SummaryTagsDescription/SummaryTagsDescription.component'; +import SummaryPanelSkeleton from 'components/Skeleton/SummaryPanelSkeleton/SummaryPanelSkeleton.component'; +import { ExplorePageTabs } from 'enums/Explore.enum'; +import { TagLabel } from 'generated/type/tagLabel'; +import React, { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { Link } from 'react-router-dom'; -import { getDashboardDetailsPath } from '../../../../constants/constants'; +import { + DRAWER_NAVIGATION_OPTIONS, + getEntityOverview, +} from 'utils/EntityUtils'; +import SVGIcons from 'utils/SvgUtils'; import { SummaryEntityType } from '../../../../enums/EntitySummary.enum'; -import { SearchIndex } from '../../../../enums/search.enum'; import { Mlmodel } from '../../../../generated/entity/data/mlmodel'; -import { getEntityName } from '../../../../utils/CommonUtils'; import { getFormattedEntityData } from '../../../../utils/EntitySummaryPanelUtils'; -import TableDataCardTitle from '../../../common/table-data-card-v2/TableDataCardTitle.component'; import SummaryList from '../SummaryList/SummaryList.component'; import { BasicEntityInfo } from '../SummaryList/SummaryList.interface'; interface MlModelSummaryProps { entityDetails: Mlmodel; + componentType?: string; + tags?: TagLabel[]; + isLoading?: boolean; } -interface BasicMlModelInfo { - algorithm: string; - target?: string; - server?: ReactNode; - dashboard?: ReactNode; -} - -function MlModelSummary({ entityDetails }: MlModelSummaryProps) { +function MlModelSummary({ + entityDetails, + componentType = DRAWER_NAVIGATION_OPTIONS.explore, + tags, + isLoading, +}: MlModelSummaryProps) { const { t } = useTranslation(); - const basicMlModelInfo: BasicMlModelInfo = useMemo( - () => ({ - algorithm: entityDetails.algorithm, - target: entityDetails.target, - server: entityDetails.server ? ( - {entityDetails.server} - ) : undefined, - dashboard: entityDetails.dashboard ? ( - - {getEntityName(entityDetails.dashboard)} - - ) : undefined, - }), + const entityInfo = useMemo( + () => getEntityOverview(ExplorePageTabs.MLMODELS, entityDetails), [entityDetails] ); @@ -68,55 +60,91 @@ function MlModelSummary({ entityDetails }: MlModelSummaryProps) { [entityDetails] ); - return ( - <> - - - - - - - {Object.keys(basicMlModelInfo).map((fieldName) => { - const value = - basicMlModelInfo[fieldName as keyof BasicMlModelInfo]; + const isExplore = useMemo( + () => componentType === DRAWER_NAVIGATION_OPTIONS.explore, + [componentType] + ); - return ( - - - - {startCase(fieldName)} - - - {value ? value : '-'} - - - - ); - })} - - - - - - - - {t('label.feature-plural')} - - - - - - - + return ( + + <> + + + + {entityInfo.map((info) => { + const isOwner = info.name === t('label.owner'); + + return info.visible?.includes(componentType) ? ( + + + {!isOwner ? ( + + + {info.name} + + + ) : null} + + {info.isLink ? ( + + {info.value} + {info.isExternal ? ( + + ) : null} + + ) : ( + + {info.value} + + )} + + + + ) : null; + })} + + + + + + {!isExplore ? ( + <> + + + + ) : null} + + + + + {t('label.feature-plural')} + + + + + + + + ); } diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/MlModelSummary/MlModelSummary.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/MlModelSummary/MlModelSummary.test.tsx index 5fa179adc8f..198cc54195e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/MlModelSummary/MlModelSummary.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/MlModelSummary/MlModelSummary.test.tsx @@ -20,16 +20,6 @@ import { } from '../mocks/MlModelSummary.mock'; import MlModelSummary from './MlModelSummary.component'; -jest.mock( - '../../../common/table-data-card-v2/TableDataCardTitle.component', - () => - jest - .fn() - .mockImplementation(() => ( -
TableDataCardTitle
- )) -); - jest.mock('../SummaryList/SummaryList.component', () => jest .fn() @@ -42,17 +32,15 @@ describe('MlModelSummary component tests', () => { wrapper: MemoryRouter, }); - const mlModelTitle = screen.getByTestId('TableDataCardTitle'); - const algorithmLabel = screen.getByTestId('algorithm-label'); - const targetLabel = screen.getByTestId('target-label'); - const serverLabel = screen.getByTestId('server-label'); - const dashboardLabel = screen.getByTestId('dashboard-label'); - const algorithmValue = screen.getByTestId('algorithm-value'); - const targetValue = screen.getByTestId('target-value'); - const serverValue = screen.getByTestId('server-value'); - const dashboardValue = screen.getByTestId('dashboard-value'); + const algorithmLabel = screen.getByTestId('label.algorithm-label'); + const targetLabel = screen.getByTestId('label.target-label'); + const serverLabel = screen.getByTestId('label.server-label'); + const dashboardLabel = screen.getByTestId('label.dashboard-label'); + const algorithmValue = screen.getByTestId('label.algorithm-value'); + const targetValue = screen.getByTestId('label.target-value'); + const serverValue = screen.getByTestId('label.server-value'); + const dashboardValue = screen.getByTestId('label.dashboard-value'); - expect(mlModelTitle).toBeInTheDocument(); expect(algorithmLabel).toBeInTheDocument(); expect(targetLabel).toBeInTheDocument(); expect(serverLabel).toBeInTheDocument(); @@ -68,14 +56,14 @@ describe('MlModelSummary component tests', () => { wrapper: MemoryRouter, }); - const algorithmLabel = screen.getByTestId('algorithm-label'); - const targetLabel = screen.queryByTestId('target-label'); - const serverLabel = screen.queryByTestId('server-label'); - const dashboardLabel = screen.queryByTestId('dashboard-label'); - const algorithmValue = screen.getByTestId('algorithm-value'); - const targetValue = screen.getByTestId('target-value'); - const serverValue = screen.getByTestId('server-value'); - const dashboardValue = screen.getByTestId('dashboard-value'); + const algorithmLabel = screen.getByTestId('label.algorithm-label'); + const targetLabel = screen.queryByTestId('label.target-label'); + const serverLabel = screen.queryByTestId('label.server-label'); + const dashboardLabel = screen.queryByTestId('label.dashboard-label'); + const algorithmValue = screen.getByTestId('label.algorithm-value'); + const targetValue = screen.getByTestId('label.target-value'); + const serverValue = screen.getByTestId('label.server-value'); + const dashboardValue = screen.getByTestId('label.dashboard-value'); expect(algorithmLabel).toBeInTheDocument(); expect(targetLabel).toBeInTheDocument(); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/PipelineSummary/PipelineSummary.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/PipelineSummary/PipelineSummary.component.tsx index 345cc626909..449f1d40079 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/PipelineSummary/PipelineSummary.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/PipelineSummary/PipelineSummary.component.tsx @@ -12,23 +12,37 @@ */ import { Col, Divider, Row, Space, Typography } from 'antd'; +import classNames from 'classnames'; +import SummaryTagsDescription from 'components/common/SummaryTagsDescription/SummaryTagsDescription.component'; +import SummaryPanelSkeleton from 'components/Skeleton/SummaryPanelSkeleton/SummaryPanelSkeleton.component'; +import { ExplorePageTabs } from 'enums/Explore.enum'; +import { TagLabel } from 'generated/type/tagLabel'; import React, { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; -import { Link } from 'react-router-dom'; +import { + DRAWER_NAVIGATION_OPTIONS, + getEntityOverview, +} from 'utils/EntityUtils'; +import SVGIcons from 'utils/SvgUtils'; import { SummaryEntityType } from '../../../../enums/EntitySummary.enum'; -import { SearchIndex } from '../../../../enums/search.enum'; import { Pipeline } from '../../../../generated/entity/data/pipeline'; import { getFormattedEntityData } from '../../../../utils/EntitySummaryPanelUtils'; -import SVGIcons from '../../../../utils/SvgUtils'; -import TableDataCardTitle from '../../../common/table-data-card-v2/TableDataCardTitle.component'; import SummaryList from '../SummaryList/SummaryList.component'; import { BasicEntityInfo } from '../SummaryList/SummaryList.interface'; interface PipelineSummaryProps { entityDetails: Pipeline; + componentType?: string; + tags?: TagLabel[]; + isLoading?: boolean; } -function PipelineSummary({ entityDetails }: PipelineSummaryProps) { +function PipelineSummary({ + entityDetails, + componentType = DRAWER_NAVIGATION_OPTIONS.explore, + tags, + isLoading, +}: PipelineSummaryProps) { const { t } = useTranslation(); const formattedTasksData: BasicEntityInfo[] = useMemo( @@ -36,63 +50,105 @@ function PipelineSummary({ entityDetails }: PipelineSummaryProps) { [entityDetails] ); + const entityInfo = useMemo( + () => getEntityOverview(ExplorePageTabs.PIPELINES, entityDetails), + [entityDetails] + ); + + const isExplore = useMemo( + () => componentType === DRAWER_NAVIGATION_OPTIONS.explore, + [componentType] + ); + return ( - <> - - - - - - - - {`${t('label.pipeline')} ${t('label.url-uppercase')}`} - - - {entityDetails.pipelineUrl ? ( - - - - {entityDetails.name} - - - - - ) : ( - '-' - )} - - - - - - - - - {t('label.task-plural')} - - - - - - - + + <> + + + + {entityInfo.map((info) => { + const isOwner = info.name === t('label.owner'); + + return info.visible?.includes(componentType) ? ( + + + {!isOwner ? ( + + + {info.name} + + + ) : null} + + {info.isLink ? ( + + + {info.value} + {info.isExternal ? ( + + ) : null} + + + ) : ( + + {info.value} + + )} + + + + ) : null; + })} + + + + + + {!isExplore ? ( + <> + + + + ) : null} + + + + + {t('label.task-plural')} + + + + + + + + ); } diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/PipelineSummary/PipelineSummary.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/PipelineSummary/PipelineSummary.test.tsx index d8d930684c4..934a521c25a 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/PipelineSummary/PipelineSummary.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/PipelineSummary/PipelineSummary.test.tsx @@ -14,19 +14,10 @@ import { render, screen } from '@testing-library/react'; import React from 'react'; import { MemoryRouter } from 'react-router-dom'; +import { DRAWER_NAVIGATION_OPTIONS } from 'utils/EntityUtils'; import { mockPipelineEntityDetails } from '../mocks/PipelineSummary.mock'; import PipelineSummary from './PipelineSummary.component'; -jest.mock( - '../../../common/table-data-card-v2/TableDataCardTitle.component', - () => - jest - .fn() - .mockImplementation(() => ( -
TableDataCardTitle
- )) -); - jest.mock('../SummaryList/SummaryList.component', () => jest .fn() @@ -34,24 +25,72 @@ jest.mock('../SummaryList/SummaryList.component', () => ); describe('PipelineSummary component tests', () => { - it('Component should render properly', () => { + it('Component should render properly, when loaded in the Explore page.', () => { render(, { wrapper: MemoryRouter, }); - const pipelineTitle = screen.getByTestId('TableDataCardTitle'); const pipelineUrlLabel = screen.getByTestId('pipeline-url-label'); const pipelineUrlValue = screen.getByTestId('pipeline-link-name'); const tasksHeader = screen.getByTestId('tasks-header'); const summaryList = screen.getByTestId('SummaryList'); - expect(pipelineTitle).toBeInTheDocument(); expect(pipelineUrlLabel).toBeInTheDocument(); expect(pipelineUrlValue).toContainHTML(mockPipelineEntityDetails.name); expect(tasksHeader).toBeInTheDocument(); expect(summaryList).toBeInTheDocument(); }); + it('Component should render properly, when loaded in the Lineage page.', async () => { + const labels = [ + 'pipeline-url-label', + 'label.pipeline label.url-uppercase-value', + 'label.service-label', + 'label.tier-label', + ]; + + const values = [ + 'label.service-value', + 'label.owner-value', + 'label.tier-value', + ]; + render( + , + { + wrapper: MemoryRouter, + } + ); + + const schemaHeader = screen.getAllByTestId('schema-header'); + const tags = screen.getByText('label.tag-plural'); + const noTags = screen.getByText('label.no-tags-added'); + const pipelineName = screen.getAllByTestId('pipeline-link-name'); + + const viewerContainer = screen.getByTestId('viewer-container'); + const summaryList = screen.getByTestId('SummaryList'); + const ownerLabel = screen.queryByTestId('label.owner-label'); + + labels.forEach((label) => + expect(screen.getByTestId(label)).toBeInTheDocument() + ); + values.forEach((value) => + expect(screen.getByTestId(value)).toBeInTheDocument() + ); + + expect(ownerLabel).not.toBeInTheDocument(); + + expect(schemaHeader[0]).toBeInTheDocument(); + expect(tags).toBeInTheDocument(); + expect(pipelineName[0]).toBeInTheDocument(); + expect(noTags).toBeInTheDocument(); + + expect(summaryList).toBeInTheDocument(); + expect(viewerContainer).toBeInTheDocument(); + }); + it('If the pipeline url is not present in pipeline details, "-" should be displayed as pipeline url value', () => { render( { } ); - const pipelineUrlValue = screen.getByTestId('pipeline-url-value'); + const pipelineUrlValue = screen.getByTestId( + 'label.pipeline label.url-uppercase-value' + ); expect(pipelineUrlValue).toContainHTML('-'); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/SummaryList/SummaryList.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/SummaryList/SummaryList.component.tsx index 9050104672e..12a4366742f 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/SummaryList/SummaryList.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/SummaryList/SummaryList.component.tsx @@ -32,7 +32,9 @@ export default function SummaryList({ {isEmpty(formattedEntityData) ? (
- {t('message.no-data-available')} + + {t('message.no-data-available')} +
) : ( formattedEntityData.map((entity) => 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 3072dd1fe22..cb8b7a21c1f 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 @@ -49,8 +49,12 @@ function SummaryListItem({ {entityDetails.type && ( - {`${t('label.type')}:`} - + {`${t( + 'label.type' + )}:`} + {entityDetails.type} @@ -64,10 +68,12 @@ function SummaryListItem({ - {`${t( + {`${t( 'label.algorithm' )}:`} - + {entityDetails.algorithm} @@ -99,8 +105,8 @@ function SummaryListItem({ )}
- - + + {entityDetails.description ? ( - + ); } diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/TableSummary/TableSummary.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/TableSummary/TableSummary.component.tsx index a32bc44705b..946b3d1ce6e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/TableSummary/TableSummary.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/TableSummary/TableSummary.component.tsx @@ -14,38 +14,56 @@ import { Col, Divider, Row, Typography } from 'antd'; import { AxiosError } from 'axios'; import classNames from 'classnames'; +import SummaryTagsDescription from 'components/common/SummaryTagsDescription/SummaryTagsDescription.component'; +import SummaryPanelSkeleton from 'components/Skeleton/SummaryPanelSkeleton/SummaryPanelSkeleton.component'; +import { ExplorePageTabs } from 'enums/Explore.enum'; import { isEmpty, isUndefined } from 'lodash'; -import React, { useEffect, useMemo, useState } from 'react'; +import { + default as React, + useCallback, + useEffect, + useMemo, + useState, +} from 'react'; import { useTranslation } from 'react-i18next'; +import { Link } from 'react-router-dom'; import { getLatestTableProfileByFqn, getTableQueryByTableId, } from 'rest/tableAPI'; import { getListTestCase } from 'rest/testAPI'; +import { + DRAWER_NAVIGATION_OPTIONS, + getEntityOverview, +} from 'utils/EntityUtils'; +import SVGIcons from 'utils/SvgUtils'; import { API_RES_MAX_SIZE } from '../../../../constants/constants'; import { INITIAL_TEST_RESULT_SUMMARY } from '../../../../constants/profiler.constant'; import { SummaryEntityType } from '../../../../enums/EntitySummary.enum'; -import { SearchIndex } from '../../../../enums/search.enum'; -import { Table, TableType } from '../../../../generated/entity/data/table'; +import { Table } from '../../../../generated/entity/data/table'; import { Include } from '../../../../generated/type/include'; import { formatNumberWithComma, - formTwoDigitNmber, + formTwoDigitNmber as formTwoDigitNumber, } from '../../../../utils/CommonUtils'; import { updateTestResults } from '../../../../utils/DataQualityAndProfilerUtils'; import { getFormattedEntityData } from '../../../../utils/EntitySummaryPanelUtils'; import { generateEntityLink } from '../../../../utils/TableUtils'; import { showErrorToast } from '../../../../utils/ToastUtils'; -import TableDataCardTitle from '../../../common/table-data-card-v2/TableDataCardTitle.component'; import { OverallTableSummeryType, TableTestsType, } from '../../../TableProfiler/TableProfiler.interface'; import SummaryList from '../SummaryList/SummaryList.component'; import { BasicEntityInfo } from '../SummaryList/SummaryList.interface'; -import { BasicTableInfo, TableSummaryProps } from './TableSummary.interface'; +import { TableSummaryProps } from './TableSummary.interface'; -function TableSummary({ entityDetails }: TableSummaryProps) { +function TableSummary({ + entityDetails, + componentType = DRAWER_NAVIGATION_OPTIONS.explore, + tags, + isLoading, +}: TableSummaryProps) { const { t } = useTranslation(); const [tableDetails, setTableDetails] = useState(entityDetails); const [tableTests, setTableTests] = useState({ @@ -53,6 +71,11 @@ function TableSummary({ entityDetails }: TableSummaryProps) { results: INITIAL_TEST_RESULT_SUMMARY, }); + const isExplore = useMemo( + () => componentType === DRAWER_NAVIGATION_OPTIONS.explore, + [componentType] + ); + const isTableDeleted = useMemo(() => entityDetails.deleted, [entityDetails]); const fetchAllTests = async () => { @@ -84,13 +107,13 @@ function TableSummary({ entityDetails }: TableSummaryProps) { } }; - const fetchProfilerData = async () => { + const fetchProfilerData = useCallback(async () => { try { const profileResponse = await getLatestTableProfileByFqn( entityDetails?.fullyQualifiedName || '' ); - const { profile } = profileResponse; + const { profile, tableConstraints } = profileResponse; const queriesResponse = await getTableQueryByTableId( entityDetails.id || '' @@ -100,7 +123,7 @@ function TableSummary({ entityDetails }: TableSummaryProps) { setTableDetails((prev) => { if (prev) { - return { ...prev, profile, tableQueries }; + return { ...prev, profile, tableQueries, tableConstraints }; } else { return {} as Table; } @@ -113,7 +136,7 @@ function TableSummary({ entityDetails }: TableSummaryProps) { }) ); } - }; + }, [entityDetails]); const overallSummary: OverallTableSummeryType[] | undefined = useMemo(() => { if (isUndefined(tableDetails.profile)) { @@ -142,31 +165,27 @@ function TableSummary({ entityDetails }: TableSummaryProps) { }, { title: `${t('label.test-plural')} ${t('label.passed')}`, - value: formTwoDigitNmber(tableTests.results.success), + value: formTwoDigitNumber(tableTests.results.success), className: 'success', }, { title: `${t('label.test-plural')} ${t('label.aborted')}`, - value: formTwoDigitNmber(tableTests.results.aborted), + value: formTwoDigitNumber(tableTests.results.aborted), className: 'aborted', }, { title: `${t('label.test-plural')} ${t('label.failed')}`, - value: formTwoDigitNmber(tableTests.results.failed), + value: formTwoDigitNumber(tableTests.results.failed), className: 'failed', }, ]; }, [tableDetails, tableTests]); - const { tableType, columns, tableQueries } = tableDetails; + const { columns } = tableDetails; - const basicTableInfo: BasicTableInfo = useMemo( - () => ({ - Type: tableType || TableType.Regular, - Queries: tableQueries?.length ? `${tableQueries?.length}` : '-', - Columns: columns?.length ? `${columns?.length}` : '-', - }), - [tableType, columns, tableQueries] + const entityInfo = useMemo( + () => getEntityOverview(ExplorePageTabs.TABLES, tableDetails), + [tableDetails] ); const formattedColumnsData: BasicEntityInfo[] = useMemo( @@ -182,102 +201,148 @@ function TableSummary({ entityDetails }: TableSummaryProps) { useEffect(() => { if (!isEmpty(entityDetails)) { setTableDetails(entityDetails); - fetchAllTests(); - !isTableDeleted && fetchProfilerData(); + if ( + !isTableDeleted && + entityDetails.service?.type === 'databaseService' + ) { + fetchProfilerData(); + fetchAllTests(); + } } }, [entityDetails]); return ( - <> - - - - - - - {Object.keys(basicTableInfo).map((fieldName) => ( - - - - {fieldName} - - - {basicTableInfo[fieldName as keyof BasicTableInfo]} - - - - ))} - - - - + + <> + + + + {entityInfo.map((info) => { + const isOwner = info.name === t('label.owner'); - - - - {t('label.profiler-amp-data-quality')} - - - - {isUndefined(overallSummary) ? ( - - {t('message.no-profiler-enabled-summary-message')} - - ) : ( - - {overallSummary.map((field) => ( - - - - - {field.title} - - - - + + {!isOwner ? ( + + + {info.name} + + + ) : null} + + {info.isLink ? ( + + {info.value} + {info.isExternal ? ( + + ) : null} + + ) : ( + + {info.value} + )} - data-testid={`${field.title}-value`}> - {field.value} - - - - - ))} + + + + ) : null; + })} - )} - - - - - - - {t('label.schema')} - - - - - - - + + + + + + {!isExplore ? ( + <> + + + + ) : null} + + + + + {t('label.profiler-amp-data-quality')} + + + + {isUndefined(overallSummary) ? ( + + {t('message.no-profiler-enabled-summary-message')} + + ) : ( + + {overallSummary.map((field) => ( + + + + + {field.title} + + + + + {field.value} + + + + + ))} + + )} + + + + + + + + + {t('label.schema')} + + + + + + + + ); } diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/TableSummary/TableSummary.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/TableSummary/TableSummary.interface.ts index e71a62d46ba..bd41cb24fb2 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/TableSummary/TableSummary.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/TableSummary/TableSummary.interface.ts @@ -11,10 +11,17 @@ * limitations under the License. */ -import { Table, TableType } from '../../../../generated/entity/data/table'; +import { + Table, + TableType, + TagLabel, +} from '../../../../generated/entity/data/table'; export interface TableSummaryProps { entityDetails: Table; + componentType?: string; + tags?: TagLabel[]; + isLoading?: boolean; } export interface BasicTableInfo { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/TableSummary/TableSummary.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/TableSummary/TableSummary.test.tsx index 9f9ba1317ae..804d1f69d9f 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/TableSummary/TableSummary.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/TableSummary/TableSummary.test.tsx @@ -13,7 +13,9 @@ import { act, render, screen } from '@testing-library/react'; import React from 'react'; +import { MemoryRouter } from 'react-router-dom'; import { getLatestTableProfileByFqn } from 'rest/tableAPI'; +import { DRAWER_NAVIGATION_OPTIONS } from 'utils/EntityUtils'; import { mockTableEntityDetails } from '../mocks/TableSummary.mock'; import TableSummary from './TableSummary.component'; @@ -30,16 +32,6 @@ jest.mock('rest/tableAPI', () => ({ .mockImplementation(() => mockTableEntityDetails), })); -jest.mock( - '../../../common/table-data-card-v2/TableDataCardTitle.component', - () => - jest - .fn() - .mockImplementation(() => ( -
TableDataCardTitle
- )) -); - jest.mock('../SummaryList/SummaryList.component', () => jest .fn() @@ -47,33 +39,93 @@ jest.mock('../SummaryList/SummaryList.component', () => ); describe('TableSummary component tests', () => { - it('Component should render properly', async () => { + it('Component should render properly, when loaded in the Explore page.', async () => { await act(async () => { render(); }); - const tableTitle = screen.getByTestId('TableDataCardTitle'); const profilerHeader = screen.getByTestId('profiler-header'); const schemaHeader = screen.getByTestId('schema-header'); - const typeLabel = screen.getByTestId('Type-label'); - const queriesLabel = screen.getByTestId('Queries-label'); - const columnsLabel = screen.getByTestId('Columns-label'); - const typeValue = screen.getByTestId('Type-value'); - const queriesValue = screen.getByTestId('Queries-value'); - const columnsValue = screen.getByTestId('Columns-value'); + const typeLabel = screen.getByTestId('label.type-label'); + const queriesLabel = screen.getByTestId('label.query-plural-label'); + const columnsLabel = screen.getByTestId('label.column-plural-label'); + const typeValue = screen.getByTestId('label.type-value'); + const queriesValue = screen.getByTestId('label.query-plural-value'); + const columnsValue = screen.getByTestId('label.column-plural-value'); const noProfilerPlaceholder = screen.getByTestId( 'no-profiler-enabled-message' ); const summaryList = screen.getByTestId('SummaryList'); - expect(tableTitle).toBeInTheDocument(); expect(profilerHeader).toBeInTheDocument(); expect(schemaHeader).toBeInTheDocument(); expect(typeLabel).toBeInTheDocument(); expect(queriesLabel).toBeInTheDocument(); expect(columnsLabel).toBeInTheDocument(); expect(typeValue).toContainHTML('Regular'); - expect(queriesValue).toContainHTML('2'); + expect(queriesValue.textContent).toBe('2 past week'); + expect(columnsValue).toContainHTML('2'); + expect(noProfilerPlaceholder).toContainHTML( + 'message.no-profiler-enabled-summary-message' + ); + expect(summaryList).toBeInTheDocument(); + }); + + it('Component should render properly, when loaded in the Lineage page.', async () => { + const labels = [ + 'label.service-label', + 'label.type-label', + 'label.database-label', + 'label.schema-label', + 'label.query-plural-label', + 'label.column-plural-label', + ]; + + const values = [ + 'label.type-value', + 'label.service-value', + 'label.database-value', + 'label.schema-value', + ]; + render( + , + { + wrapper: MemoryRouter, + } + ); + + const profilerHeader = screen.getByTestId('profiler-header'); + const schemaHeader = screen.getAllByTestId('schema-header'); + const queriesLabel = screen.getByTestId('label.query-plural-label'); + const columnsLabel = screen.getByTestId('label.column-plural-label'); + const typeValue = screen.getByTestId('label.type-value'); + const queriesValue = screen.getByTestId('label.query-plural-value'); + const columnsValue = screen.getByTestId('label.column-plural-value'); + const noProfilerPlaceholder = screen.getByTestId( + 'no-profiler-enabled-message' + ); + const ownerLabel = screen.queryByTestId('label.owner-label'); + + const summaryList = screen.getByTestId('SummaryList'); + + expect(ownerLabel).not.toBeInTheDocument(); + + labels.forEach((label) => + expect(screen.getByTestId(label)).toBeInTheDocument() + ); + values.forEach((value) => + expect(screen.getByTestId(value)).toBeInTheDocument() + ); + + expect(profilerHeader).toBeInTheDocument(); + expect(schemaHeader[0]).toBeInTheDocument(); + expect(queriesLabel).toBeInTheDocument(); + expect(columnsLabel).toBeInTheDocument(); + expect(typeValue).toContainHTML('Regular'); + expect(queriesValue.textContent).toBe('2 past week'); expect(columnsValue).toContainHTML('2'); expect(noProfilerPlaceholder).toContainHTML( 'message.no-profiler-enabled-summary-message' diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/TopicSummary/TopicSummary.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/TopicSummary/TopicSummary.component.tsx index 6d94ba0289d..5edc7f17cfe 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/TopicSummary/TopicSummary.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/TopicSummary/TopicSummary.component.tsx @@ -12,55 +12,80 @@ */ import { Col, Divider, Row, Typography } from 'antd'; +import SummaryTagsDescription from 'components/common/SummaryTagsDescription/SummaryTagsDescription.component'; +import SummaryPanelSkeleton from 'components/Skeleton/SummaryPanelSkeleton/SummaryPanelSkeleton.component'; +import { getTeamAndUserDetailsPath } from 'constants/constants'; import { isArray, isEmpty } from 'lodash'; -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; +import { Link } from 'react-router-dom'; import { getTopicByFqn } from 'rest/topicsAPI'; +import { + DRAWER_NAVIGATION_OPTIONS, + getOwnerNameWithProfilePic, +} from 'utils/EntityUtils'; +import { showErrorToast } from 'utils/ToastUtils'; import { SummaryEntityType } from '../../../../enums/EntitySummary.enum'; -import { SearchIndex } from '../../../../enums/search.enum'; -import { Topic } from '../../../../generated/entity/data/topic'; +import { TagLabel, Topic } from '../../../../generated/entity/data/topic'; import { getFormattedEntityData } from '../../../../utils/EntitySummaryPanelUtils'; import { bytesToSize } from '../../../../utils/StringsUtils'; -import { showErrorToast } from '../../../../utils/ToastUtils'; import { getConfigObject } from '../../../../utils/TopicDetailsUtils'; -import TableDataCardTitle from '../../../common/table-data-card-v2/TableDataCardTitle.component'; import { TopicConfigObjectInterface } from '../../../TopicDetails/TopicDetails.interface'; import SummaryList from '../SummaryList/SummaryList.component'; import { BasicEntityInfo } from '../SummaryList/SummaryList.interface'; interface TopicSummaryProps { entityDetails: Topic; + componentType?: string; + tags?: TagLabel[]; + isLoading?: boolean; } -function TopicSummary({ entityDetails }: TopicSummaryProps) { +function TopicSummary({ + entityDetails, + componentType = DRAWER_NAVIGATION_OPTIONS.explore, + tags, + isLoading, +}: TopicSummaryProps) { const { t } = useTranslation(); + const [topicDetails, setTopicDetails] = useState(entityDetails); + const isExplore = useMemo( + () => componentType === DRAWER_NAVIGATION_OPTIONS.explore, + [componentType] + ); const topicConfig = useMemo(() => { - const configs = getConfigObject(topicDetails); + const combined = { ...topicDetails, ...entityDetails }; + const configs = getConfigObject(combined); return { ...configs, 'Retention Size': bytesToSize(configs['Retention Size'] ?? 0), 'Max Message Size': bytesToSize(configs['Max Message Size'] ?? 0), }; - }, [topicDetails]); + }, [entityDetails, topicDetails]); - const formattedSchemaFieldsData: BasicEntityInfo[] = useMemo( - () => - getFormattedEntityData( - SummaryEntityType.SCHEMAFIELD, - topicDetails.messageSchema?.schemaFields - ), - [topicDetails] - ); + const ownerDetails = useMemo(() => { + const owner = entityDetails.owner; - const fetchExtraTopicInfo = async () => { + return { + value: + getOwnerNameWithProfilePic(owner) || + t('label.no-entity', { + entity: t('label.owner'), + }), + url: getTeamAndUserDetailsPath(owner?.name || ''), + isLink: owner?.name ? true : false, + }; + }, [entityDetails, topicDetails]); + + const fetchExtraTopicInfo = useCallback(async () => { try { - const res = await getTopicByFqn( - entityDetails.fullyQualifiedName ?? '', - '' - ); + const res = await getTopicByFqn(entityDetails.fullyQualifiedName ?? '', [ + 'tags', + 'owner', + ]); const { partitions, messageSchema } = res; @@ -73,73 +98,104 @@ function TopicSummary({ entityDetails }: TopicSummaryProps) { }) ); } - }; - - useEffect(() => { - fetchExtraTopicInfo(); }, [entityDetails]); + const formattedSchemaFieldsData: BasicEntityInfo[] = useMemo( + () => + getFormattedEntityData( + SummaryEntityType.SCHEMAFIELD, + topicDetails.messageSchema?.schemaFields + ), + [topicDetails] + ); + + useEffect(() => { + if (entityDetails.service?.type === 'messagingService') { + fetchExtraTopicInfo(); + } + }, [entityDetails, componentType]); + return ( - <> - -
- - - - - {Object.keys(topicConfig).map((fieldName) => { - const value = - topicConfig[fieldName as keyof TopicConfigObjectInterface]; + + <> + + {!isExplore ? ( + + {ownerDetails.isLink ? ( + + {ownerDetails.value} + + ) : ( + + {ownerDetails.value} + + )} + + ) : null} + + + {Object.keys(topicConfig).map((fieldName) => { + const value = + topicConfig[fieldName as keyof TopicConfigObjectInterface]; - const fieldValue = isArray(value) ? value.join(', ') : value; + const fieldValue = isArray(value) ? value.join(', ') : value; - return ( - - - - {fieldName} - - - {fieldValue ? fieldValue : '-'} - - - - ); - })} - - - - - - - - {t('label.schema')} - - - - {isEmpty(topicDetails.messageSchema?.schemaFields) ? ( -
- - {t('message.no-data-available')} - -
- ) : ( - - )} - - - + return ( + + + + + {fieldName} + + + + {fieldValue ? fieldValue : '-'} + + + + ); + })} + + + + + + {!isExplore ? ( + <> + + + + ) : null} + + + + + {t('label.schema')} + + + + {isEmpty(topicDetails?.messageSchema?.schemaFields) ? ( +
+ + + {t('message.no-data-available')} + + +
+ ) : ( + + )} + + + + ); } diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/TopicSummary/TopicSummary.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/TopicSummary/TopicSummary.test.tsx index fce8bd827c3..6c78baabea3 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/TopicSummary/TopicSummary.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/TopicSummary/TopicSummary.test.tsx @@ -20,16 +20,6 @@ import { } from '../mocks/TopicSummary.mock'; import TopicSummary from './TopicSummary.component'; -jest.mock( - '../../../common/table-data-card-v2/TableDataCardTitle.component', - () => - jest - .fn() - .mockImplementation(() => ( -
TableDataCardTitle
- )) -); - jest.mock('../SummaryList/SummaryList.component', () => jest .fn() @@ -46,7 +36,6 @@ describe('TopicSummary component tests', () => { render(); }); - const topicTitle = screen.getByTestId('TableDataCardTitle'); const partitionsLabel = screen.getByTestId('Partitions-label'); const replicationFactorLabel = screen.getByTestId( 'Replication Factor-label' @@ -64,13 +53,12 @@ describe('TopicSummary component tests', () => { const schemaHeader = screen.getByTestId('schema-header'); const summaryList = screen.getByTestId('SummaryList'); - expect(topicTitle).toBeInTheDocument(); expect(partitionsLabel).toBeInTheDocument(); expect(replicationFactorLabel).toBeInTheDocument(); expect(retentionSizeLabel).toBeInTheDocument(); expect(cleanUpPoliciesLabel).toBeInTheDocument(); expect(maxMessageSizeLabel).toBeInTheDocument(); - expect(partitionsValue).toContainHTML('128'); + expect(partitionsValue).toContainHTML('-'); expect(replicationFactorValue).toContainHTML('4'); expect(retentionSizeValue).toContainHTML('1018.83 MB'); expect(cleanUpPoliciesValue).toContainHTML('delete'); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/mocks/TableSummary.mock.ts b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/mocks/TableSummary.mock.ts index 8dac0bcc098..dd875d98de7 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/mocks/TableSummary.mock.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/mocks/TableSummary.mock.ts @@ -97,5 +97,48 @@ export const mockTableEntityDetails: Table = { queryDate: mockDate, }, ], + service: { + id: '0875717c-5855-427c-8dd6-92d4cbfe7c51', + type: 'databaseService', + name: 'sample_data', + fullyQualifiedName: 'sample_data', + deleted: false, + href: 'http://localhost:8585/api/v1/services/databaseServices/0875717c-5855-427c-8dd6-92d4cbfe7c51', + }, + usageSummary: { + dailyStats: { + count: 0, + percentileRank: 0, + }, + weeklyStats: { + count: 2, + percentileRank: 0, + }, + monthlyStats: { + count: 0, + percentileRank: 0, + }, + date: '2023-02-01' as unknown as Date, + }, + databaseSchema: { + id: '406d4782-b480-42a4-ab8b-e6fed20f3eef', + type: 'databaseSchema', + name: 'shopify', + fullyQualifiedName: 'sample_data.ecommerce_db.shopify', + description: + 'This **mock** database contains schema related to shopify sales and orders with related dimension tables.', + deleted: false, + href: 'http://localhost:8585/api/v1/databaseSchemas/406d4782-b480-42a4-ab8b-e6fed20f3eef', + }, + database: { + id: '78a58be0-26a9-4ac8-b515-067db85bbb41', + type: 'database', + name: 'ecommerce_db', + fullyQualifiedName: 'sample_data.ecommerce_db', + description: + 'This **mock** database contains schemas related to shopify sales and orders with related dimension tables.', + deleted: false, + href: 'http://localhost:8585/api/v1/databases/78a58be0-26a9-4ac8-b515-067db85bbb41', + }, followers: [], }; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Explore/Explore.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Explore/Explore.test.tsx index 592954303ed..03aecb851c6 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Explore/Explore.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/Explore.test.tsx @@ -47,6 +47,14 @@ jest.mock('components/searched-data/SearchedData', () => { )); }); +jest.mock('./EntitySummaryPanel/EntitySummaryPanel.component', () => + jest + .fn() + .mockImplementation(() => ( +
EntitySummaryPanel
+ )) +); + const mockFunction = jest.fn(); jest.mock('../containers/PageLayoutV1', () => diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Skeleton/CommonSkeletons/LabelCountSkeleton/LabelCountSkeleton.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Skeleton/CommonSkeletons/LabelCountSkeleton/LabelCountSkeleton.component.tsx index e292c638efa..651c8151427 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Skeleton/CommonSkeletons/LabelCountSkeleton/LabelCountSkeleton.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Skeleton/CommonSkeletons/LabelCountSkeleton/LabelCountSkeleton.component.tsx @@ -22,12 +22,14 @@ const LabelCountSkeleton = ({ labelProps, selectProps, countProps, + firstColSize = 20, + secondColSize = 4, ...props }: LabelCountSkeletonProps) => { return ( {isSelect || isLabel ? ( -
+
{isSelect ? (
@@ -58,7 +60,7 @@ const LabelCountSkeleton = ({
) : null} -
+ {isCount ? ( { labelProps?: SkeletonProps; selectProps?: SkeletonProps; countProps?: SkeletonProps; + firstColSize?: number; + secondColSize?: number; } export type EntityListSkeletonProps = SkeletonInterface & diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Skeleton/SummaryPanelSkeleton/SummaryPanelSkeleton.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Skeleton/SummaryPanelSkeleton/SummaryPanelSkeleton.component.tsx new file mode 100644 index 00000000000..36c8065abe1 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/Skeleton/SummaryPanelSkeleton/SummaryPanelSkeleton.component.tsx @@ -0,0 +1,52 @@ +/* + * 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 ButtonSkeleton from 'components/Skeleton/CommonSkeletons/ControlElements/ControlElements.component'; +import LabelCountSkeleton from 'components/Skeleton/CommonSkeletons/LabelCountSkeleton/LabelCountSkeleton.component'; +import { SkeletonInterface } from 'components/Skeleton/Skeleton.interfaces'; +import { getSkeletonMockData } from 'components/Skeleton/SkeletonUtils/Skeleton.utils'; +import { uniqueId } from 'lodash'; +import React from 'react'; + +const SummaryPanelSkeleton = ({ loading, children }: SkeletonInterface) => { + return loading ? ( +
+ +
+ {getSkeletonMockData(5).map(() => ( + + ))} + + + + {getSkeletonMockData(10).map(() => ( + + ))} + + + + ) : ( + children + ); +}; + +export default SummaryPanelSkeleton; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/TopicDetails/TopicDetails.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/TopicDetails/TopicDetails.interface.ts index e9b9ee15645..e147fdc6bf0 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/TopicDetails/TopicDetails.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/TopicDetails/TopicDetails.interface.ts @@ -98,6 +98,7 @@ export interface TopicDetailsProps { } export interface TopicConfigObjectInterface { + Owner?: Record; Partitions: number; 'Replication Factor'?: number; 'Retention Size'?: number; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/SummaryTagsDescription/SummaryTagsDescription.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/SummaryTagsDescription/SummaryTagsDescription.component.tsx new file mode 100644 index 00000000000..bae356f4cc9 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/SummaryTagsDescription/SummaryTagsDescription.component.tsx @@ -0,0 +1,81 @@ +/* + * 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, Divider, Row, Typography } from 'antd'; +import TagsViewer from 'components/Tag/TagsViewer/tags-viewer'; +import { TagLabel } from 'generated/type/tagLabel'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { ReactComponent as TagIcon } from '../../../assets/svg/tag-grey.svg'; +import { EntityData } from '../PopOverCard/EntityPopOverCard'; +import RichTextEditorPreviewer from '../rich-text-editor/RichTextEditorPreviewer'; + +const SummaryTagsDescription = ({ + tags, + entityDetail, +}: { + tags: TagLabel[]; + entityDetail: EntityData; +}) => { + const { t } = useTranslation(); + + return ( + <> + + + + {t('label.tag-plural')} + + + +
+ {tags.length > 0 ? ( + <> + + + + ) : ( + + {t('label.no-tags-added')} + + )} +
+ + + + + + + + + {t('label.description')} + + + +
+ {entityDetail.description?.trim() ? ( + + ) : ( + + {t('label.no-data-found')} + + )} +
+ + + + ); +}; + +export default SummaryTagsDescription; 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 index d3aa8a7830d..fb2044d282b 100644 --- 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 @@ -12,6 +12,7 @@ */ import { Button, Typography } from 'antd'; +import classNames from 'classnames'; import { toString } from 'lodash'; import React, { useMemo } from 'react'; import { Link } from 'react-router-dom'; @@ -32,6 +33,7 @@ interface TableDataCardTitleProps { id?: string; searchIndex: SearchIndex | EntityType; source: SourceType; + isPanel?: boolean; handleLinkClick?: (e: React.MouseEvent) => void; } @@ -41,6 +43,7 @@ const TableDataCardTitle = ({ searchIndex, source, handleLinkClick, + isPanel = false, }: TableDataCardTitleProps) => { const isTourRoute = location.pathname.includes(ROUTES.TOUR); @@ -76,7 +79,12 @@ const TableDataCardTitle = ({ level={5} title={displayName}> {title} 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/common/table-data-card-v2/TableDataCardTitle.less index e3cb6d4c382..19774fe1dff 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/common/table-data-card-v2/TableDataCardTitle.less @@ -10,7 +10,7 @@ * 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 { @@ -24,3 +24,11 @@ color: @link-btn-color; } } + +.button-hover { + .ant-btn-link > span { + &:hover { + color: @primary-color; + } + } +} 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 5f7f9cabf06..072b20790d6 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 @@ -1161,6 +1161,7 @@ "entity-fetch-error": "Error while fetching {{entity}}", "entity-fetch-version-error": "Error while fetching {{entity}} versions {{version}}", "entity-updating-error": "Error while updating {{entity}}", + "error-selected-node-name-details": "Error while getting {{selectedNodeName}} details", "error-while-renewing-id-token-with-message": "Error while renewing id token from Auth0 SSO: {{message}}", "feed-post-error": "Error while posting the message!", "fetch-entity-permissions-error": "Unable to get permission for {{entity}}.", diff --git a/openmetadata-ui/src/main/resources/ui/src/rest/mlModelAPI.ts b/openmetadata-ui/src/main/resources/ui/src/rest/mlModelAPI.ts index 74b2e0ca6b6..050303def11 100644 --- a/openmetadata-ui/src/main/resources/ui/src/rest/mlModelAPI.ts +++ b/openmetadata-ui/src/main/resources/ui/src/rest/mlModelAPI.ts @@ -41,7 +41,7 @@ export const getMlModelVersion = async (id: string, version: string) => { export const getMlModelByFQN = async ( fqn: string, - arrQueryFields: string, + arrQueryFields: string | string[], include = Include.All ) => { const url = getURLWithQueryFields( diff --git a/openmetadata-ui/src/main/resources/ui/src/rest/topicsAPI.ts b/openmetadata-ui/src/main/resources/ui/src/rest/topicsAPI.ts index a4c70f0fa35..b58f28696b0 100644 --- a/openmetadata-ui/src/main/resources/ui/src/rest/topicsAPI.ts +++ b/openmetadata-ui/src/main/resources/ui/src/rest/topicsAPI.ts @@ -67,7 +67,7 @@ export const getTopicDetails = ( export const getTopicByFqn = async ( fqn: string, - arrQueryFields: string | TabSpecificField[] + arrQueryFields: string[] | string | TabSpecificField[] ) => { const url = getURLWithQueryFields( `/topics/name/${fqn}`, 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 f59b2e8997e..a670ad062c8 100644 --- a/openmetadata-ui/src/main/resources/ui/src/styles/app.less +++ b/openmetadata-ui/src/main/resources/ui/src/styles/app.less @@ -49,30 +49,6 @@ font-weight: 600; } -// font size -.text-xs { - font-size: 12px; -} -.text-lg { - font-size: 18px; -} -.text-sm { - font-size: 14px; -} -.text-base { - font-size: 1rem /* 16px */; - line-height: 1.5rem /* 24px */; -} -.text-xl { - font-size: 1.25rem /* 20px */; - line-height: 1.75rem /* 28px */; -} - -.text-2xl { - font-size: 1.5rem /* 24px */; - line-height: 2rem /* 32px */; -} - // text color .text-primary { color: @primary; diff --git a/openmetadata-ui/src/main/resources/ui/src/styles/variables.less b/openmetadata-ui/src/main/resources/ui/src/styles/variables.less index 6fcf10412d2..5225f57c441 100644 --- a/openmetadata-ui/src/main/resources/ui/src/styles/variables.less +++ b/openmetadata-ui/src/main/resources/ui/src/styles/variables.less @@ -17,6 +17,8 @@ @success-color: #008376; @warning-color: #ffc34e; @error-color: #ff4c3b; +@failed-color: #cb2431; +@aborted-color: #efae2f; @info-color: #1890ff; @text-color: #000000; @text-color-secondary: #37352f; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/EntityLineageUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/EntityLineageUtils.tsx index 39c798bdf1a..f0a16329347 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/EntityLineageUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/EntityLineageUtils.tsx @@ -12,6 +12,7 @@ */ import { CheckOutlined, LeftOutlined, RightOutlined } from '@ant-design/icons'; +import { Button, Typography } from 'antd'; import { CustomEdgeData, CustomElement, @@ -91,13 +92,20 @@ export const getHeaderLabel = ( {name || prepareLabel(type, fqn, false)} ) : ( - - - {name || prepareLabel(type, fqn, false)} + + + - + )} ); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/EntitySummaryPanelUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/EntitySummaryPanelUtils.tsx index 4eba885a850..38ef73d5c63 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/EntitySummaryPanelUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/EntitySummaryPanelUtils.tsx @@ -57,7 +57,9 @@ export const getFormattedEntityData = ( title: ( - {getEntityName(chart)} + + {getEntityName(chart)} + @@ -73,7 +75,9 @@ export const getFormattedEntityData = ( title: ( - {task.name} + + {task.name} + 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 61fa643f504..91c5faf5895 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/EntityUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/EntityUtils.tsx @@ -13,11 +13,14 @@ import { Popover } from 'antd'; import { EntityData } from 'components/common/PopOverCard/EntityPopOverCard'; +import ProfilePicture from 'components/common/ProfilePicture/ProfilePicture'; import { LeafNodes, LineagePos, } from 'components/EntityLineage/EntityLineage.interface'; import { ResourceEntity } from 'components/PermissionProvider/PermissionProvider.interface'; +import { ExplorePageTabs } from 'enums/Explore.enum'; +import { Mlmodel } from 'generated/entity/data/mlmodel'; import i18next from 'i18next'; import { isEmpty, isNil, isUndefined, lowerCase, startCase } from 'lodash'; import { Bucket } from 'Models'; @@ -25,11 +28,11 @@ import React, { Fragment } from 'react'; import { Link } from 'react-router-dom'; import { FQN_SEPARATOR_CHAR } from '../constants/char.constants'; import { + getDashboardDetailsPath, getDatabaseDetailsPath, getDatabaseSchemaDetailsPath, getServiceDetailsPath, getTableDetailsPath, - getTeamAndUserDetailsPath, } from '../constants/constants'; import { AssetsType, EntityType, FqnPart } from '../enums/entity.enum'; import { SearchIndex } from '../enums/search.enum'; @@ -42,6 +45,7 @@ import { ColumnJoins, JoinedWith, Table, + TableType, } from '../generated/entity/data/table'; import { Topic } from '../generated/entity/data/topic'; import { Edge, EntityLineage } from '../generated/type/entityLineage'; @@ -49,6 +53,7 @@ import { EntityReference } from '../generated/type/entityUsage'; import { TagLabel } from '../generated/type/tagLabel'; import { getEntityName, + getOwnerValue, getPartialNameFromTableFQN, getTableFQNFromColumnFQN, } from './CommonUtils'; @@ -59,10 +64,15 @@ import { } from './TableUtils'; import { getTableTags } from './TagsUtils'; +export enum DRAWER_NAVIGATION_OPTIONS { + explore = 'Explore', + lineage = 'Lineage', +} + export const getEntityTags = ( type: string, - entityDetail: Table | Pipeline | Dashboard | Topic -): Array => { + entityDetail: Table | Pipeline | Dashboard | Topic | Mlmodel +): Array => { switch (type) { case EntityType.TABLE: { const tableTags: Array = [ @@ -72,10 +82,10 @@ export const getEntityTags = ( return tableTags; } - case EntityType.PIPELINE: { - return entityDetail.tags || []; - } - case EntityType.DASHBOARD: { + case EntityType.PIPELINE: + case EntityType.DASHBOARD: + case EntityType.TOPIC: + case EntityType.MLMODEL: { return entityDetail.tags || []; } @@ -84,19 +94,38 @@ export const getEntityTags = ( } }; +export const getOwnerNameWithProfilePic = ( + owner: EntityReference | undefined +) => + owner ? ( +
+ {' '} + + {getEntityName(owner)} +
+ ) : null; + export const getEntityOverview = ( type: string, - entityDetail: EntityData, - serviceType: string + entityDetail: EntityData ): Array<{ name: string; value: string | number | React.ReactNode; isLink: boolean; isExternal?: boolean; url?: string; + visible?: Array; + dataTestId?: string; }> => { + const NO_DATA = '-'; + switch (type) { - case EntityType.TABLE: { + case ExplorePageTabs.TABLES: { const { fullyQualifiedName, owner, @@ -104,31 +133,56 @@ export const getEntityOverview = ( usageSummary, profile, columns, + tableType, } = entityDetail as Table; const [service, database, schema] = getPartialNameFromTableFQN( fullyQualifiedName ?? '', [FqnPart.Service, FqnPart.Database, FqnPart.Schema], FQN_SEPARATOR_CHAR ).split(FQN_SEPARATOR_CHAR); + const tier = getTierFromTableTags(tags || []); + const usage = !isNil(usageSummary?.weeklyStats?.percentileRank) ? getUsagePercentile(usageSummary?.weeklyStats?.percentileRank || 0) - : '--'; - const queries = usageSummary?.weeklyStats?.count.toLocaleString() || '--'; + : '-'; + + const queries = usageSummary?.weeklyStats?.count.toLocaleString() || '0'; const overview = [ + { + name: i18next.t('label.owner'), + value: + getOwnerNameWithProfilePic(owner) || + i18next.t('label.no-entity', { + entity: i18next.t('label.owner'), + }), + url: getOwnerValue(owner as EntityReference), + isLink: owner?.name ? true : false, + visible: [DRAWER_NAVIGATION_OPTIONS.lineage], + }, + { + name: i18next.t('label.type'), + value: tableType || TableType.Regular, + isLink: false, + visible: [ + DRAWER_NAVIGATION_OPTIONS.lineage, + DRAWER_NAVIGATION_OPTIONS.explore, + ], + }, { name: i18next.t('label.service'), - value: service, + value: service || NO_DATA, url: getServiceDetailsPath( service, ServiceCategory.DATABASE_SERVICES ), isLink: true, + visible: [DRAWER_NAVIGATION_OPTIONS.lineage], }, { name: i18next.t('label.database'), - value: database, + value: database || NO_DATA, url: getDatabaseDetailsPath( getPartialNameFromTableFQN( fullyQualifiedName ?? '', @@ -137,10 +191,11 @@ export const getEntityOverview = ( ) ), isLink: true, + visible: [DRAWER_NAVIGATION_OPTIONS.lineage], }, { name: i18next.t('label.schema'), - value: schema, + value: schema || NO_DATA, url: getDatabaseSchemaDetailsPath( getPartialNameFromTableFQN( fullyQualifiedName ?? '', @@ -149,126 +204,217 @@ export const getEntityOverview = ( ) ), isLink: true, - }, - { - name: i18next.t('label.owner'), - value: getEntityName(owner) || '--', - url: getTeamAndUserDetailsPath(owner?.name || ''), - isLink: owner ? owner.type === 'team' : false, + visible: [DRAWER_NAVIGATION_OPTIONS.lineage], }, { name: i18next.t('label.tier'), - value: tier ? tier.split(FQN_SEPARATOR_CHAR)[1] : '--', + value: tier ? tier.split(FQN_SEPARATOR_CHAR)[1] : NO_DATA, isLink: false, + visible: [DRAWER_NAVIGATION_OPTIONS.lineage], }, { name: i18next.t('label.usage'), - value: usage, + value: usage || NO_DATA, isLink: false, + visible: [DRAWER_NAVIGATION_OPTIONS.lineage], }, { name: i18next.t('label.query-plural'), value: `${queries} past week`, isLink: false, + visible: [ + DRAWER_NAVIGATION_OPTIONS.lineage, + DRAWER_NAVIGATION_OPTIONS.explore, + ], }, { name: i18next.t('label.column-plural'), - value: columns ? columns.length : '--', + value: columns ? columns.length : NO_DATA, isLink: false, + visible: [ + DRAWER_NAVIGATION_OPTIONS.lineage, + DRAWER_NAVIGATION_OPTIONS.explore, + ], }, { name: i18next.t('label.row-plural'), - value: profile && profile?.rowCount ? profile.rowCount : '--', + value: profile && profile?.rowCount ? profile.rowCount : NO_DATA, isLink: false, + visible: [DRAWER_NAVIGATION_OPTIONS.lineage], }, ]; return overview; } - case EntityType.PIPELINE: { - const { owner, tags, pipelineUrl, service, fullyQualifiedName } = + case ExplorePageTabs.PIPELINES: { + const { owner, tags, pipelineUrl, service, displayName } = entityDetail as Pipeline; const tier = getTierFromTableTags(tags || []); const overview = [ + { + name: i18next.t('label.owner'), + value: + getOwnerNameWithProfilePic(owner) || + i18next.t('label.no-entity', { + entity: i18next.t('label.owner'), + }), + url: getOwnerValue(owner as EntityReference), + isLink: owner?.name ? true : false, + visible: [DRAWER_NAVIGATION_OPTIONS.lineage], + }, + { + name: `${i18next.t('label.pipeline')} ${i18next.t( + 'label.url-uppercase' + )}`, + dataTestId: 'pipeline-url-label', + value: displayName || NO_DATA, + url: pipelineUrl, + isLink: true, + isExternal: true, + visible: [ + DRAWER_NAVIGATION_OPTIONS.lineage, + DRAWER_NAVIGATION_OPTIONS.explore, + ], + }, { name: i18next.t('label.service'), - value: service?.name as string, + value: (service?.name as string) || NO_DATA, url: getServiceDetailsPath( service?.name as string, ServiceCategory.PIPELINE_SERVICES ), isLink: true, - }, - { - name: i18next.t('label.owner'), - value: getEntityName(owner) || '--', - url: getTeamAndUserDetailsPath(owner?.name || ''), - isLink: owner ? owner.type === 'team' : false, + isExternal: false, + visible: [DRAWER_NAVIGATION_OPTIONS.lineage], }, { name: i18next.t('label.tier'), - value: tier ? tier.split(FQN_SEPARATOR_CHAR)[1] : '--', + value: tier ? tier.split(FQN_SEPARATOR_CHAR)[1] : NO_DATA, isLink: false, - }, - { - name: `${serviceType} ${i18next.t('label.url-lowercase')}`, - value: fullyQualifiedName?.split(FQN_SEPARATOR_CHAR)[1] as string, - url: pipelineUrl as string, - isLink: true, - isExternal: true, + visible: [DRAWER_NAVIGATION_OPTIONS.lineage], }, ]; return overview; } - case EntityType.DASHBOARD: { - const { - owner, - tags, - dashboardUrl, - service, - fullyQualifiedName, - displayName, - } = entityDetail as Dashboard; + case ExplorePageTabs.DASHBOARDS: { + const { owner, tags, dashboardUrl, service, displayName } = + entityDetail as Dashboard; const tier = getTierFromTableTags(tags || []); const overview = [ + { + name: i18next.t('label.owner'), + value: + getOwnerNameWithProfilePic(owner) || + i18next.t('label.no-entity', { + entity: i18next.t('label.owner'), + }), + url: getOwnerValue(owner as EntityReference), + isLink: owner?.name ? true : false, + visible: [DRAWER_NAVIGATION_OPTIONS.lineage], + }, + { + name: `${i18next.t('label.dashboard')} ${i18next.t( + 'label.url-uppercase' + )}`, + value: displayName || NO_DATA, + url: dashboardUrl, + isLink: true, + isExternal: true, + visible: [ + DRAWER_NAVIGATION_OPTIONS.lineage, + DRAWER_NAVIGATION_OPTIONS.explore, + ], + }, { name: i18next.t('label.service'), - value: service?.name as string, + value: (service?.fullyQualifiedName as string) || NO_DATA, url: getServiceDetailsPath( service?.name as string, ServiceCategory.DASHBOARD_SERVICES ), + isExternal: false, isLink: true, + visible: [DRAWER_NAVIGATION_OPTIONS.lineage], }, - { - name: i18next.t('label.owner'), - value: getEntityName(owner) || '--', - url: getTeamAndUserDetailsPath(owner?.name || ''), - isLink: owner ? owner.type === 'team' : false, - }, + { name: i18next.t('label.tier'), - value: tier ? tier.split(FQN_SEPARATOR_CHAR)[1] : '--', + value: tier ? tier.split(FQN_SEPARATOR_CHAR)[1] : NO_DATA, isLink: false, - }, - { - name: `${serviceType} ${i18next.t('label.url-lowercase')}`, - value: - displayName || - (fullyQualifiedName?.split(FQN_SEPARATOR_CHAR)[1] as string), - url: dashboardUrl as string, - isLink: true, - isExternal: true, + isExternal: false, + visible: [DRAWER_NAVIGATION_OPTIONS.lineage], }, ]; return overview; } + case ExplorePageTabs.MLMODELS: { + const { algorithm, target, server, dashboard, owner } = + entityDetail as Mlmodel; + + const overview = [ + { + name: i18next.t('label.owner'), + value: + getOwnerNameWithProfilePic(owner) || + i18next.t('label.no-entity', { + entity: i18next.t('label.owner'), + }), + url: getOwnerValue(owner as EntityReference), + isLink: owner?.name ? true : false, + visible: [DRAWER_NAVIGATION_OPTIONS.lineage], + }, + { + name: i18next.t('label.algorithm'), + value: algorithm || NO_DATA, + url: '', + isLink: false, + visible: [ + DRAWER_NAVIGATION_OPTIONS.lineage, + DRAWER_NAVIGATION_OPTIONS.explore, + ], + }, + { + name: i18next.t('label.target'), + value: target || NO_DATA, + url: '', + isLink: false, + visible: [ + DRAWER_NAVIGATION_OPTIONS.lineage, + DRAWER_NAVIGATION_OPTIONS.explore, + ], + }, + { + name: i18next.t('label.server'), + value: server || NO_DATA, + url: server, + isLink: true, + isExternal: true, + visible: [ + DRAWER_NAVIGATION_OPTIONS.lineage, + DRAWER_NAVIGATION_OPTIONS.explore, + ], + }, + { + name: i18next.t('label.dashboard'), + value: getEntityName(dashboard) || NO_DATA, + url: getDashboardDetailsPath(dashboard?.fullyQualifiedName as string), + isLink: true, + isExternal: false, + visible: [ + DRAWER_NAVIGATION_OPTIONS.lineage, + DRAWER_NAVIGATION_OPTIONS.explore, + ], + }, + ]; + + return overview; + } default: return []; } 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 ff37619f1a7..2c83237332b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/TableUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/TableUtils.tsx @@ -90,7 +90,7 @@ export const getUsagePercentile = (pctRank: number, isLiteral = false) => { const ordinalPercentile = ordinalize(percentile); const usagePercentile = `${ isLiteral ? t('label.usage') : '' - } - ${ordinalPercentile} ${t('label.pctile-lowercase')}`; + } ${ordinalPercentile} ${t('label.pctile-lowercase')}`; return usagePercentile; };