From 91a794aaa0f36bf3fa7f65653b415fb8bc83a2f3 Mon Sep 17 00:00:00 2001 From: Aniket Katkar Date: Thu, 12 Jan 2023 18:05:08 +0530 Subject: [PATCH] UI: Improved summary panel (#9470) * Improved topic summary panel content * added functionality to show nested column and schema details inside the EntitySummaryPanel. * added unit tests for SummaryListItems component * added unit tests for summary panel components code optimizations and improvements * fixed localization in SummaryListItems component fixed unit tests * fixed codesmells * - added schema type field to show for topic summary - fixed width issue for nested field details in summary panel - added unit tests for EntitySummaryPanelUtils - moved imports on top in EntitySummaryPanel test * fixed failing unit tests * Fixed failing unit tests Co-authored-by: Chirag Madlani <12962843+chirag-madlani@users.noreply.github.com> --- .../DashboardSummary.component.tsx | 57 +++-- .../DashboardSummary.test.tsx | 83 ++++++ .../EntitySummaryPanel.component.tsx | 6 +- .../EntitySummaryPanel.test.tsx | 191 ++++++++++++++ .../MlModelSummary.component.tsx | 51 ++-- .../MlModelSummary/MlModelSummary.test.tsx | 89 +++++++ .../PipelineSummary.component.tsx | 81 +++--- .../PipelineSummary/PipelineSummary.test.tsx | 69 +++++ .../SummaryList/SummaryList.component.tsx | 197 +++----------- .../SummaryList/SummaryList.interface.ts | 30 +-- .../SummaryList/SummaryList.style.less | 30 +++ .../SummaryList/SummaryList.test.tsx | 89 +++++++ .../SummaryListItems.component.tsx | 120 +++++++++ .../SummaryListItems.interface.ts | 19 ++ .../SummaryListItems.test.tsx | 133 ++++++++++ .../TableSummary/TableSummary.component.tsx | 37 ++- .../TableSummary/TableSummary.test.tsx | 136 ++++++++++ .../TopicSummary/TopicSummary.component.tsx | 73 +++++- .../TopicSummary/TopicSummary.test.tsx | 111 ++++++++ .../mocks/DashboardSummary.mock.ts | 99 +++++++ .../mocks/MlModelSummary.mock.ts | 146 +++++++++++ .../mocks/PipelineSummary.mock.ts | 59 +++++ .../mocks/SummaryList.mock.ts | 64 +++++ .../mocks/SummaryListItems.mock.tsx | 69 +++++ .../mocks/TableSummary.mock.ts | 101 ++++++++ .../mocks/TopicSummary.mock.ts | 100 ++++++++ .../TopicDetails/TopicDetails.interface.ts | 2 + .../ui/src/enums/EntitySummary.enum.ts | 20 ++ .../utils/EntitySummaryPanelUtils.test.tsx | 61 +++++ .../ui/src/utils/EntitySummaryPanelUtils.tsx | 111 ++++++++ .../ui/src/utils/TopicDetailsUtils.ts | 1 + .../mocks/EntitySummaryPanelUtils.mock.tsx | 241 ++++++++++++++++++ 32 files changed, 2398 insertions(+), 278 deletions(-) create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/DashboardSummary/DashboardSummary.test.tsx create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/EntitySummaryPanel.test.tsx create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/MlModelSummary/MlModelSummary.test.tsx create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/PipelineSummary/PipelineSummary.test.tsx create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/SummaryList/SummaryList.style.less create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/SummaryList/SummaryList.test.tsx create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/SummaryList/SummaryListItems/SummaryListItems.component.tsx create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/SummaryList/SummaryListItems/SummaryListItems.interface.ts create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/SummaryList/SummaryListItems/SummaryListItems.test.tsx create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/TableSummary/TableSummary.test.tsx create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/TopicSummary/TopicSummary.test.tsx create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/mocks/DashboardSummary.mock.ts create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/mocks/MlModelSummary.mock.ts create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/mocks/PipelineSummary.mock.ts create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/mocks/SummaryList.mock.ts create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/mocks/SummaryListItems.mock.tsx create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/mocks/TableSummary.mock.ts create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/mocks/TopicSummary.mock.ts create mode 100644 openmetadata-ui/src/main/resources/ui/src/enums/EntitySummary.enum.ts create mode 100644 openmetadata-ui/src/main/resources/ui/src/utils/EntitySummaryPanelUtils.test.tsx create mode 100644 openmetadata-ui/src/main/resources/ui/src/utils/EntitySummaryPanelUtils.tsx create mode 100644 openmetadata-ui/src/main/resources/ui/src/utils/mocks/EntitySummaryPanelUtils.mock.tsx 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 271d67aa2cc..1a610b18056 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 @@ -14,16 +14,19 @@ import { Col, Divider, Row, Space, Typography } from 'antd'; import { AxiosError } from 'axios'; import { ChartType } from 'pages/DashboardDetailsPage/DashboardDetailsPage.component'; -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { Link } from 'react-router-dom'; +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; @@ -57,6 +60,11 @@ function DashboardSummary({ entityDetails }: DashboardSummaryProps) { fetchChartsDetails(); }, [entityDetails]); + const formattedChartsData: BasicEntityInfo[] = useMemo( + () => getFormattedEntityData(SummaryEntityType.CHART, charts), + [charts] + ); + return ( <> @@ -69,24 +77,33 @@ function DashboardSummary({ entityDetails }: DashboardSummaryProps) { - + {`${t('label.dashboard')} ${t('label.url-uppercase')}`} - - - - - {entityDetails.name} - - - - + + {entityDetails.dashboardUrl ? ( + + + + {entityDetails.name} + + + + + ) : ( + '-' + )} @@ -94,12 +111,14 @@ function DashboardSummary({ entityDetails }: DashboardSummaryProps) { - + {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 new file mode 100644 index 00000000000..2901dd5dc28 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/DashboardSummary/DashboardSummary.test.tsx @@ -0,0 +1,83 @@ +/* + * 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 { act, render, screen } from '@testing-library/react'; +import React from 'react'; +import { MemoryRouter } from 'react-router-dom'; +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() + .mockImplementation(() =>
SummaryList
) +); + +jest.mock('../../../../utils/DashboardDetailsUtils', () => ({ + fetchCharts: jest.fn().mockImplementation(() => mockFetchChartsResponse), +})); + +describe('DashboardSummary component tests', () => { + it('Component should render properly', async () => { + await act(async () => { + render(, { + wrapper: MemoryRouter, + }); + }); + + const dashboardTitle = screen.getByTestId('TableDataCardTitle'); + const dashboardUrlLabel = screen.getByTestId('dashboard-url-label'); + const dashboardUrlValue = screen.getByTestId('dashboard-link-name'); + const chartsHeader = screen.getByTestId('charts-header'); + const summaryList = screen.getByTestId('SummaryList'); + + expect(dashboardTitle).toBeInTheDocument(); + expect(dashboardUrlLabel).toBeInTheDocument(); + expect(dashboardUrlValue).toContainHTML(mockDashboardEntityDetails.name); + expect(chartsHeader).toBeInTheDocument(); + expect(summaryList).toBeInTheDocument(); + }); + + it('If the dashboard url is not present in dashboard details, "-" should be displayed as dashboard url value', async () => { + await act(async () => { + render( + , + { + wrapper: MemoryRouter, + } + ); + + const dashboardUrlValue = screen.getByTestId('dashboard-url-value'); + + expect(dashboardUrlValue).toContainHTML('-'); + }); + }); +}); 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 51b28de5fad..ddb85db6dbc 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 @@ -68,7 +68,11 @@ export default function EntitySummaryPanel({ return (
{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 new file mode 100644 index 00000000000..5f7eddb0694 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/EntitySummaryPanel.test.tsx @@ -0,0 +1,191 @@ +/* + * 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 { act, render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; +import { ExplorePageTabs } from '../../../enums/Explore.enum'; +import EntitySummaryPanel from './EntitySummaryPanel.component'; +import { mockDashboardEntityDetails } from './mocks/DashboardSummary.mock'; +import { mockMlModelEntityDetails } from './mocks/MlModelSummary.mock'; +import { mockPipelineEntityDetails } from './mocks/PipelineSummary.mock'; +import { mockTableEntityDetails } from './mocks/TableSummary.mock'; +import { mockTopicEntityDetails } from './mocks/TopicSummary.mock'; + +const mockHandleClosePanel = jest.fn(); + +jest.mock('./TableSummary/TableSummary.component', () => + jest + .fn() + .mockImplementation(() => ( +
TableSummary
+ )) +); + +jest.mock('./TopicSummary/TopicSummary.component', () => + jest + .fn() + .mockImplementation(() => ( +
TopicSummary
+ )) +); + +jest.mock('./DashboardSummary/DashboardSummary.component', () => + jest + .fn() + .mockImplementation(() => ( +
DashboardSummary
+ )) +); + +jest.mock('./PipelineSummary/PipelineSummary.component', () => + jest + .fn() + .mockImplementation(() => ( +
PipelineSummary
+ )) +); + +jest.mock('./MlModelSummary/MlModelSummary.component', () => + jest + .fn() + .mockImplementation(() => ( +
MlModelSummary
+ )) +); + +jest.mock('react-router-dom', () => ({ + useParams: jest.fn().mockImplementation(() => ({ tab: 'table' })), +})); + +describe('EntitySummaryPanel component tests', () => { + it('TableSummary should render for table data', async () => { + render( + + ); + + const tableSummary = screen.getByTestId('TableSummary'); + const closeIcon = screen.getByTestId('summary-panel-close-icon'); + + expect(tableSummary).toBeInTheDocument(); + expect(closeIcon).toBeInTheDocument(); + + await act(async () => { + userEvent.click(closeIcon); + }); + + expect(mockHandleClosePanel).toHaveBeenCalledTimes(1); + }); + + it('TopicSummary should render for topics data', async () => { + render( + + ); + + const topicSummary = screen.getByTestId('TopicSummary'); + const closeIcon = screen.getByTestId('summary-panel-close-icon'); + + expect(topicSummary).toBeInTheDocument(); + expect(closeIcon).toBeInTheDocument(); + + await act(async () => { + userEvent.click(closeIcon); + }); + + expect(mockHandleClosePanel).toHaveBeenCalledTimes(1); + }); + + it('DashboardSummary should render for dashboard data', async () => { + render( + + ); + + const dashboardSummary = screen.getByTestId('DashboardSummary'); + const closeIcon = screen.getByTestId('summary-panel-close-icon'); + + expect(dashboardSummary).toBeInTheDocument(); + expect(closeIcon).toBeInTheDocument(); + + await act(async () => { + userEvent.click(closeIcon); + }); + + expect(mockHandleClosePanel).toHaveBeenCalledTimes(1); + }); + + it('PipelineSummary should render for pipeline data', async () => { + render( + + ); + + const pipelineSummary = screen.getByTestId('PipelineSummary'); + const closeIcon = screen.getByTestId('summary-panel-close-icon'); + + expect(pipelineSummary).toBeInTheDocument(); + expect(closeIcon).toBeInTheDocument(); + + await act(async () => { + userEvent.click(closeIcon); + }); + + expect(mockHandleClosePanel).toHaveBeenCalledTimes(1); + }); + + it('MlModelSummary should render for mlModel data', async () => { + render( + + ); + + const mlModelSummary = screen.getByTestId('MlModelSummary'); + const closeIcon = screen.getByTestId('summary-panel-close-icon'); + + expect(mlModelSummary).toBeInTheDocument(); + expect(closeIcon).toBeInTheDocument(); + + await act(async () => { + userEvent.click(closeIcon); + }); + + expect(mockHandleClosePanel).toHaveBeenCalledTimes(1); + }); +}); 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 4ff242b5f63..f9580094cbe 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,15 +12,19 @@ */ import { Col, Divider, Row, Typography } from 'antd'; +import { startCase } from 'lodash'; import React, { ReactNode, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { Link } from 'react-router-dom'; import { getDashboardDetailsPath } from '../../../../constants/constants'; +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; @@ -55,6 +59,15 @@ function MlModelSummary({ entityDetails }: MlModelSummaryProps) { [entityDetails] ); + const formattedFeaturesData: BasicEntityInfo[] = useMemo( + () => + getFormattedEntityData( + SummaryEntityType.MLFEATURE, + entityDetails.mlFeatures + ), + [entityDetails] + ); + return ( <> @@ -70,22 +83,22 @@ function MlModelSummary({ entityDetails }: MlModelSummaryProps) { {Object.keys(basicMlModelInfo).map((fieldName) => { const value = basicMlModelInfo[fieldName as keyof BasicMlModelInfo]; - if (value) { - return ( - - - - {fieldName} - - - {basicMlModelInfo[fieldName as keyof BasicMlModelInfo]} - - - - ); - } else { - return null; - } + + return ( + + + + {startCase(fieldName)} + + + {value ? value : '-'} + + + + ); })} @@ -93,12 +106,14 @@ function MlModelSummary({ entityDetails }: MlModelSummaryProps) { - + {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 new file mode 100644 index 00000000000..5fa179adc8f --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/MlModelSummary/MlModelSummary.test.tsx @@ -0,0 +1,89 @@ +/* + * 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 { + mockMlModelEntityDetails, + mockMlModelEntityDetails1, +} 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() + .mockImplementation(() =>
SummaryList
) +); + +describe('MlModelSummary component tests', () => { + it('Component should render properly', () => { + render(, { + 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'); + + expect(mlModelTitle).toBeInTheDocument(); + expect(algorithmLabel).toBeInTheDocument(); + expect(targetLabel).toBeInTheDocument(); + expect(serverLabel).toBeInTheDocument(); + expect(dashboardLabel).toBeInTheDocument(); + expect(algorithmValue).toContainHTML('Neural Network'); + expect(targetValue).toContainHTML('ETA_time'); + expect(serverValue).toContainHTML('http://my-server.ai'); + expect(dashboardValue).toBeInTheDocument(); + }); + + it('Fields with no data should display "-" in value', () => { + render(, { + 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'); + + expect(algorithmLabel).toBeInTheDocument(); + expect(targetLabel).toBeInTheDocument(); + expect(serverLabel).toBeInTheDocument(); + expect(dashboardLabel).toBeInTheDocument(); + expect(algorithmValue).toContainHTML('Time Series'); + expect(targetValue).toContainHTML('-'); + expect(serverValue).toContainHTML('-'); + expect(dashboardValue).toContainHTML('-'); + }); +}); 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 126b05e880d..345cc626909 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,16 +12,17 @@ */ import { Col, Divider, Row, Space, Typography } from 'antd'; -import { AxiosError } from 'axios'; -import React, { useEffect, useState } from 'react'; +import React, { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { Link } from 'react-router-dom'; +import { SummaryEntityType } from '../../../../enums/EntitySummary.enum'; import { SearchIndex } from '../../../../enums/search.enum'; -import { Pipeline, Task } from '../../../../generated/entity/data/pipeline'; +import { Pipeline } from '../../../../generated/entity/data/pipeline'; +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 PipelineSummaryProps { entityDetails: Pipeline; @@ -29,28 +30,11 @@ interface PipelineSummaryProps { function PipelineSummary({ entityDetails }: PipelineSummaryProps) { const { t } = useTranslation(); - const [tasks, setTasks] = useState(); - const fetchTaskDetails = async () => { - try { - const updatedTasks = (entityDetails.tasks || []).map((task) => ({ - ...task, - taskUrl: task.taskUrl, - })); - setTasks(updatedTasks); - } catch (err) { - showErrorToast( - err as AxiosError, - t('server.entity-fetch-error', { - entity: t('label.pipeline-detail-plural-lowercase'), - }) - ); - } - }; - - useEffect(() => { - fetchTaskDetails(); - }, [entityDetails]); + const formattedTasksData: BasicEntityInfo[] = useMemo( + () => getFormattedEntityData(SummaryEntityType.TASK, entityDetails.tasks), + [entityDetails] + ); return ( <> @@ -64,24 +48,33 @@ function PipelineSummary({ entityDetails }: PipelineSummaryProps) { - + {`${t('label.pipeline')} ${t('label.url-uppercase')}`} - - - - - {entityDetails.name} - - - - + + {entityDetails.pipelineUrl ? ( + + + + {entityDetails.name} + + + + + ) : ( + '-' + )} @@ -89,12 +82,14 @@ function PipelineSummary({ entityDetails }: PipelineSummaryProps) { - + {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 new file mode 100644 index 00000000000..d8d930684c4 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/PipelineSummary/PipelineSummary.test.tsx @@ -0,0 +1,69 @@ +/* + * 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 { 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() + .mockImplementation(() =>
SummaryList
) +); + +describe('PipelineSummary component tests', () => { + it('Component should render properly', () => { + 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('If the pipeline url is not present in pipeline details, "-" should be displayed as pipeline url value', () => { + render( + , + { + wrapper: MemoryRouter, + } + ); + + const pipelineUrlValue = screen.getByTestId('pipeline-url-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 5b94136a5d4..9050104672e 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 @@ -11,175 +11,60 @@ * limitations under the License. */ -import { Col, Divider, Row, Space, Typography } from 'antd'; -import { isEmpty } from 'lodash'; -import React, { useMemo } from 'react'; +import { Collapse, Row, Typography } from 'antd'; +import { isEmpty, isUndefined } from 'lodash'; +import React from 'react'; import { useTranslation } from 'react-i18next'; -import { Link } from 'react-router-dom'; -import { ReactComponent as IconTagGrey } from '../../../../assets/svg/tag-grey.svg'; -import { MAX_CHAR_LIMIT_ENTITY_SUMMARY } from '../../../../constants/constants'; -import { getEntityName, getTagValue } from '../../../../utils/CommonUtils'; -import SVGIcons from '../../../../utils/SvgUtils'; -import { prepareConstraintIcon } from '../../../../utils/TableUtils'; -import RichTextEditorPreviewer from '../../../common/rich-text-editor/RichTextEditorPreviewer'; -import TagsViewer from '../../../tags-viewer/tags-viewer'; -import { BasicColumnInfo, SummaryListProps } from './SummaryList.interface'; +import { SummaryEntityType } from '../../../../enums/EntitySummary.enum'; +import { SummaryListProps } from './SummaryList.interface'; +import './SummaryList.style.less'; +import SummaryListItems from './SummaryListItems/SummaryListItems.component'; -const { Text, Paragraph } = Typography; +const { Text } = Typography; export default function SummaryList({ - columns, - charts, - tasks, - mlFeatures, + formattedEntityData, + entityType, }: SummaryListProps) { const { t } = useTranslation(); - const formattedColumnsData: BasicColumnInfo[] = useMemo(() => { - if (columns) { - return columns.map((column) => ({ - name: column.name, - title: {column.name}, - type: column.dataType, - tags: column.tags, - description: column.description, - constraint: column.constraint, - })); - } else if (charts) { - return charts.map((chart) => ({ - name: chart.name, - title: ( - - - {getEntityName(chart)} - - - - ), - type: chart.chartType, - tags: chart.tags, - description: chart.description, - })); - } else if (tasks) { - return tasks.map((task) => ({ - name: task.name, - title: ( - - - {task.name} - - - - ), - type: task.taskType, - tags: task.tags, - description: task.description, - })); - } else if (mlFeatures) { - return mlFeatures.map((feature) => ({ - algorithm: feature.featureAlgorithm, - name: feature.name || '--', - title: {feature.name}, - type: feature.dataType, - tags: feature.tags, - description: feature.description, - })); - } else { - return []; - } - }, [columns, charts, tasks, mlFeatures]); - return ( - {isEmpty(formattedColumnsData) ? ( + {isEmpty(formattedEntityData) ? (
{t('message.no-data-available')}
) : ( - formattedColumnsData.map((entity) => ( - - - - {columns && - prepareConstraintIcon( - entity.name, - entity.constraint, - undefined, - 'm-r-xss', - '14px' - )} - {entity.title} - - - - - {entity.type && ( - - {`${t( - 'label.type' - )}:`} - {entity.type} - - )} - - - {entity.algorithm && ( - <> - - - - - - {`${t( - 'label.algorithm' - )}:`} - - {entity.algorithm} - - - - - )} - {entity.tags && entity.tags.length !== 0 && ( - <> - - - - - - - - - getTagValue(tag) - )} - /> - - - - - )} - - - - - {entity.description ? ( - - ) : ( - t('label.no-entity', { - entity: t('label.description'), - }) - )} - - - - - - )) + formattedEntityData.map((entity) => + isEmpty(entity.children) || isUndefined(entity.children) ? ( + + ) : ( + + + } + key={`${entity.name}-collapse-panel`}> + + + + ) + ) )}
); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/SummaryList/SummaryList.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/SummaryList/SummaryList.interface.ts index 1fe664c2887..e6370395941 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/SummaryList/SummaryList.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/SummaryList/SummaryList.interface.ts @@ -12,27 +12,13 @@ */ import { ReactNode } from 'react'; -import { Chart, ChartType } from '../../../../generated/entity/data/chart'; -import { - FeatureType, - MlFeature, -} from '../../../../generated/entity/data/mlmodel'; -import { Task } from '../../../../generated/entity/data/pipeline'; -import { - Column, - Constraint, - DataType, -} from '../../../../generated/entity/data/table'; +import { SummaryEntityType } from '../../../../enums/EntitySummary.enum'; +import { ChartType } from '../../../../generated/entity/data/chart'; +import { FeatureType } from '../../../../generated/entity/data/mlmodel'; +import { Constraint, DataType } from '../../../../generated/entity/data/table'; import { TagLabel } from '../../../../generated/type/tagLabel'; -export interface SummaryListProps { - columns?: Column[]; - charts?: Chart[]; - tasks?: Task[]; - mlFeatures?: MlFeature[]; -} - -export interface BasicColumnInfo { +export interface BasicEntityInfo { algorithm?: string; name: string; title: ReactNode; @@ -40,4 +26,10 @@ export interface BasicColumnInfo { tags?: TagLabel[]; description?: string; constraint?: Constraint; + children?: BasicEntityInfo[]; +} + +export interface SummaryListProps { + formattedEntityData: BasicEntityInfo[]; + entityType?: SummaryEntityType; } diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/SummaryList/SummaryList.style.less b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/SummaryList/SummaryList.style.less new file mode 100644 index 00000000000..1f42327ae8b --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/SummaryList/SummaryList.style.less @@ -0,0 +1,30 @@ +/* + * 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. + */ + +.summary-list-collapse { + .ant-collapse-item { + .ant-collapse-header { + padding: 0px; + + .ant-collapse-arrow { + margin-right: 8px; + } + } + .ant-collapse-content { + .ant-collapse-content-box { + padding: 0px; + padding-left: 20px; + } + } + } +} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/SummaryList/SummaryList.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/SummaryList/SummaryList.test.tsx new file mode 100644 index 00000000000..dae0748a010 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/SummaryList/SummaryList.test.tsx @@ -0,0 +1,89 @@ +/* + * 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 } from '@testing-library/react'; +import React from 'react'; +import { SummaryEntityType } from '../../../../enums/EntitySummary.enum'; +import { + mockFormattedEntityData, + mockFormattedEntityDataWithChildren, +} from '../mocks/SummaryList.mock'; +import SummaryList from './SummaryList.component'; + +jest.mock('./SummaryListItems/SummaryListItems.component', () => + jest.fn().mockImplementation(({ entityDetails, isColumnsData }) => ( +
+
{entityDetails.name}
+
+ {`${isColumnsData}`} +
+
+ )) +); + +describe('SummaryList component tests', () => { + it('No data placeholder should display when an empty array is sent as a prop', () => { + const { getByText } = render(); + + const noDataPlaceholder = getByText('message.no-data-available'); + + expect(noDataPlaceholder).toBeInTheDocument(); + }); + + it('Summary list items should render properly for given formatted entity data', () => { + const { getByTestId } = render( + + ); + + const summaryListItem1 = getByTestId('SummaryListItems-name1'); + const summaryListItem2 = getByTestId('SummaryListItems-name2'); + const isColumnData1 = getByTestId('isColumnsData-name1'); + const isColumnData2 = getByTestId('isColumnsData-name2'); + + expect(summaryListItem1).toBeInTheDocument(); + expect(summaryListItem2).toBeInTheDocument(); + expect(isColumnData1).toContainHTML('false'); + expect(isColumnData2).toContainHTML('false'); + }); + + it('SummaryListItem component should receive isColumnsData prop true for entityType "column"', () => { + const { getByTestId } = render( + + ); + + const summaryListItem1 = getByTestId('SummaryListItems-name1'); + const summaryListItem2 = getByTestId('SummaryListItems-name2'); + const isColumnData1 = getByTestId('isColumnsData-name1'); + const isColumnData2 = getByTestId('isColumnsData-name2'); + + expect(summaryListItem1).toBeInTheDocument(); + expect(summaryListItem2).toBeInTheDocument(); + expect(isColumnData1).toContainHTML('true'); + expect(isColumnData2).toContainHTML('true'); + }); + + it('Collapse should render for entity with children', () => { + const { getByTestId, queryByTestId } = render( + + ); + + const collapse1 = getByTestId('name1-collapse'); + const collapse2 = queryByTestId('name2-collapse'); + + expect(collapse1).toBeInTheDocument(); + expect(collapse2).toBeNull(); + }); +}); 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 new file mode 100644 index 00000000000..7b7a66cb62c --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/SummaryList/SummaryListItems/SummaryListItems.component.tsx @@ -0,0 +1,120 @@ +/* + * 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, Space, Typography } from 'antd'; +import React from 'react'; +import { useTranslation } from 'react-i18next'; +import { ReactComponent as IconTagGrey } from '../../../../../assets/svg/tag-grey.svg'; +import { MAX_CHAR_LIMIT_ENTITY_SUMMARY } from '../../../../../constants/constants'; +import { getTagValue } from '../../../../../utils/CommonUtils'; +import { prepareConstraintIcon } from '../../../../../utils/TableUtils'; +import RichTextEditorPreviewer from '../../../../common/rich-text-editor/RichTextEditorPreviewer'; +import TagsViewer from '../../../../tags-viewer/tags-viewer'; +import { SummaryListItemProps } from './SummaryListItems.interface'; + +const { Text, Paragraph } = Typography; + +function SummaryListItem({ + entityDetails, + isColumnsData, +}: SummaryListItemProps) { + const { t } = useTranslation(); + + return ( + + + + {isColumnsData && + prepareConstraintIcon( + entityDetails.name, + entityDetails.constraint, + undefined, + 'm-r-xss', + '14px' + )} + {entityDetails.title} + + + + + {entityDetails.type && ( + + {`${t('label.type')}:`} + + {entityDetails.type} + + + )} + + + {entityDetails.algorithm && ( + <> + + + + + + {`${t( + 'label.algorithm' + )}:`} + + {entityDetails.algorithm} + + + + + )} + {entityDetails.tags && entityDetails.tags.length !== 0 && ( + <> + + + + + + + + + getTagValue(tag) + )} + /> + + + + + )} + + + + + {entityDetails.description ? ( + + ) : ( + t('label.no-entity', { entity: t('label.description') }) + )} + + + + + + ); +} + +export default SummaryListItem; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/SummaryList/SummaryListItems/SummaryListItems.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/SummaryList/SummaryListItems/SummaryListItems.interface.ts new file mode 100644 index 00000000000..a43ebce8c2a --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/SummaryList/SummaryListItems/SummaryListItems.interface.ts @@ -0,0 +1,19 @@ +/* + * 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 { BasicEntityInfo } from '../SummaryList.interface'; + +export interface SummaryListItemProps { + entityDetails: BasicEntityInfo; + isColumnsData?: boolean; +} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/SummaryList/SummaryListItems/SummaryListItems.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/SummaryList/SummaryListItems/SummaryListItems.test.tsx new file mode 100644 index 00000000000..6660576cfe0 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/SummaryList/SummaryListItems/SummaryListItems.test.tsx @@ -0,0 +1,133 @@ +/* + * 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 } from '@testing-library/react'; +import React from 'react'; +import { + mockEntityDetails, + mockEntityDetailsWithConstraint, + mockEntityDetailsWithoutDescription, + mockEntityDetailsWithTagsAndAlgorithm, +} from '../../mocks/SummaryListItems.mock'; +import SummaryListItem from './SummaryListItems.component'; + +jest.mock('../../../../common/rich-text-editor/RichTextEditorPreviewer', () => + jest + .fn() + .mockImplementation(() => ( +
RichTextEditorPreviewer
+ )) +); + +jest.mock('../../../../tags-viewer/tags-viewer', () => + jest + .fn() + .mockImplementation(() =>
TagsViewer
) +); + +jest.mock('../../../../../utils/TableUtils', () => ({ + prepareConstraintIcon: jest + .fn() + .mockImplementation(() =>
Constraints
), +})); + +describe('SummaryListItems component tests', () => { + it('Component should render properly with title, type and description of the entity', () => { + const { getByTestId } = render( + + ); + + const titleContainer = getByTestId('title-container'); + const title = getByTestId('title'); + const type = getByTestId('entity-type'); + const description = getByTestId('RichTextEditorPreviewer'); + + expect(titleContainer).toBeInTheDocument(); + expect(title).toContainHTML('Title'); + expect(type).toBeInTheDocument(); + expect(type).toContainHTML(mockEntityDetails.type); + expect(description).toBeInTheDocument(); + }); + + it('tags and algorithm should not render if entity has no tags and algorithm', () => { + const { queryByTestId } = render( + + ); + + const tags = queryByTestId('TagsViewer'); + const algorithm = queryByTestId('algorithm'); + + expect(tags).toBeNull(); + expect(algorithm).toBeNull(); + }); + + it('tags and algorithm should render if entity has tags and algorithm', () => { + const { getByTestId } = render( + + ); + + const tags = getByTestId('TagsViewer'); + const algorithm = getByTestId('algorithm'); + + expect(tags).toBeInTheDocument(); + expect(algorithm).toBeInTheDocument(); + expect(algorithm).toContainHTML( + mockEntityDetailsWithTagsAndAlgorithm.algorithm + ); + }); + + it('no description placeholder should be displayed for entity having no description', async () => { + const { getByText, queryByTestId } = render( + + ); + + const richTextEditorPreviewer = queryByTestId('RichTextEditorPreviewer'); + const noDescription = getByText('label.no-entity'); + + expect(richTextEditorPreviewer).toBeNull(); + expect(noDescription).toBeInTheDocument(); + }); + + it('constraint icon should not be present for entity without constraint details', async () => { + const { queryByTestId } = render( + + ); + + const richTextEditorPreviewer = queryByTestId('constraints'); + + expect(richTextEditorPreviewer).toBeNull(); + }); + + it('constraint icon should not be present for entity if isColumnsData prop is not true', async () => { + const { queryByTestId } = render( + + ); + + const richTextEditorPreviewer = queryByTestId('constraints'); + + expect(richTextEditorPreviewer).toBeNull(); + }); + + it('constraint icon should be displayed for entity with constraint details', async () => { + const { getByTestId } = render( + + ); + + const richTextEditorPreviewer = getByTestId('constraints'); + + expect(richTextEditorPreviewer).toBeInTheDocument(); + }); +}); 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 416cbc6fd21..22bdef4a3ef 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 @@ -24,6 +24,7 @@ import { import { getListTestCase } from 'rest/testAPI'; 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 { Include } from '../../../../generated/type/include'; @@ -32,6 +33,7 @@ import { formTwoDigitNmber, } 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'; @@ -40,6 +42,7 @@ import { TableTestsType, } from '../../../TableProfiler/TableProfiler.interface'; import SummaryList from '../SummaryList/SummaryList.component'; +import { BasicEntityInfo } from '../SummaryList/SummaryList.interface'; import { BasicTableInfo, TableSummaryProps } from './TableSummary.interface'; function TableSummary({ entityDetails }: TableSummaryProps) { @@ -162,6 +165,11 @@ function TableSummary({ entityDetails }: TableSummaryProps) { [tableType, columns, tableQueries] ); + const formattedColumnsData: BasicEntityInfo[] = useMemo( + () => getFormattedEntityData(SummaryEntityType.COLUMN, columns), + [columns] + ); + useEffect(() => { if (!isEmpty(entityDetails)) { setTableDetails(entityDetails); @@ -185,10 +193,13 @@ function TableSummary({ entityDetails }: TableSummaryProps) { {Object.keys(basicTableInfo).map((fieldName) => ( - + {fieldName} - + {basicTableInfo[fieldName as keyof BasicTableInfo]} @@ -201,13 +212,15 @@ function TableSummary({ entityDetails }: TableSummaryProps) { - + {t('label.profiler-amp-data-quality')} {isUndefined(overallSummary) ? ( - + {t('message.no-profiler-enabled-summary-message')} ) : ( @@ -216,7 +229,9 @@ function TableSummary({ entityDetails }: TableSummaryProps) { - + {field.title} @@ -225,7 +240,8 @@ function TableSummary({ entityDetails }: TableSummaryProps) { className={classNames( 'summary-panel-statistics-count', field.className - )}> + )} + data-testid={`${field.title}-value`}> {field.value} @@ -239,12 +255,17 @@ function TableSummary({ entityDetails }: TableSummaryProps) { - + {t('label.schema')} - + 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 new file mode 100644 index 00000000000..4d499261a62 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/TableSummary/TableSummary.test.tsx @@ -0,0 +1,136 @@ +/* + * 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 { act, render, screen } from '@testing-library/react'; +import React from 'react'; +import { getLatestTableProfileByFqn } from 'rest/tableAPI'; +import { mockTableEntityDetails } from '../mocks/TableSummary.mock'; +import TableSummary from './TableSummary.component'; + +jest.mock('rest/testAPI', () => ({ + getListTestCase: jest.fn().mockReturnValue([]), +})); + +jest.mock('rest/tableAPI', () => ({ + getLatestTableProfileByFqn: jest + .fn() + .mockImplementation(() => mockTableEntityDetails), + getTableQueryByTableId: jest + .fn() + .mockImplementation(() => mockTableEntityDetails), +})); + +jest.mock( + '../../../common/table-data-card-v2/TableDataCardTitle.component', + () => + jest + .fn() + .mockImplementation(() => ( +
TableDataCardTitle
+ )) +); + +jest.mock('../SummaryList/SummaryList.component', () => + jest + .fn() + .mockImplementation(() =>
SummaryList
) +); + +describe('TableSummary component tests', () => { + it('Component should render properly', 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 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(columnsValue).toContainHTML('2'); + expect(noProfilerPlaceholder).toContainHTML( + 'message.no-profiler-enabled-summary-message' + ); + expect(summaryList).toBeInTheDocument(); + }); + + it('Profiler data should be displayed for tables with profiler data available', async () => { + (getLatestTableProfileByFqn as jest.Mock).mockImplementationOnce(() => ({ + ...mockTableEntityDetails, + profile: { rowCount: 30, columnCount: 2, timestamp: 38478857 }, + })); + + await act(async () => { + render(); + }); + + const rowCountLabel = screen.getByTestId('label.row-count-label'); + const colCountLabel = screen.getByTestId('label.column-entity-label'); + const tableSampleLabel = screen.getByTestId( + 'label.table-entity-text %-label' + ); + const testsPassedLabel = screen.getByTestId( + 'label.test-plural label.passed-label' + ); + const testsAbortedLabel = screen.getByTestId( + 'label.test-plural label.aborted-label' + ); + const testsFailedLabel = screen.getByTestId( + 'label.test-plural label.failed-label' + ); + const rowCountValue = screen.getByTestId('label.row-count-value'); + const colCountValue = screen.getByTestId('label.column-entity-value'); + const tableSampleValue = screen.getByTestId( + 'label.table-entity-text %-value' + ); + const testsPassedValue = screen.getByTestId( + 'label.test-plural label.passed-value' + ); + const testsAbortedValue = screen.getByTestId( + 'label.test-plural label.aborted-value' + ); + const testsFailedValue = screen.getByTestId( + 'label.test-plural label.failed-value' + ); + + expect(rowCountLabel).toBeInTheDocument(); + expect(colCountLabel).toBeInTheDocument(); + expect(tableSampleLabel).toBeInTheDocument(); + expect(testsPassedLabel).toBeInTheDocument(); + expect(testsAbortedLabel).toBeInTheDocument(); + expect(testsFailedLabel).toBeInTheDocument(); + expect(rowCountValue).toContainHTML('30'); + expect(colCountValue).toContainHTML('2'); + expect(tableSampleValue).toContainHTML('100%'); + expect(testsPassedValue).toContainHTML('00'); + expect(testsAbortedValue).toContainHTML('00'); + expect(testsFailedValue).toContainHTML('00'); + }); +}); 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 9d07f4beb4a..3034cad42e6 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 @@ -13,15 +13,20 @@ import { Col, Divider, Row, Typography } from 'antd'; import { isArray } from 'lodash'; -import React, { useMemo } from 'react'; +import React, { useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; +import { getTopicByFqn } from 'rest/topicsAPI'; +import { SummaryEntityType } from '../../../../enums/EntitySummary.enum'; import { SearchIndex } from '../../../../enums/search.enum'; import { 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 SchemaEditor from '../../../schema-editor/SchemaEditor'; import { TopicConfigObjectInterface } from '../../../TopicDetails/TopicDetails.interface'; +import SummaryList from '../SummaryList/SummaryList.component'; +import { BasicEntityInfo } from '../SummaryList/SummaryList.interface'; interface TopicSummaryProps { entityDetails: Topic; @@ -29,15 +34,49 @@ interface TopicSummaryProps { function TopicSummary({ entityDetails }: TopicSummaryProps) { const { t } = useTranslation(); + const [topicDetails, setTopicDetails] = useState(entityDetails); const topicConfig = useMemo(() => { - const configs = getConfigObject(entityDetails); + const configs = getConfigObject(topicDetails); return { ...configs, 'Retention Size': bytesToSize(configs['Retention Size'] ?? 0), 'Max Message Size': bytesToSize(configs['Max Message Size'] ?? 0), }; + }, [topicDetails]); + + const formattedSchemaFieldsData: BasicEntityInfo[] = useMemo( + () => + getFormattedEntityData( + SummaryEntityType.SCHEMAFIELD, + topicDetails.messageSchema?.schemaFields + ), + [topicDetails] + ); + + const fetchExtraTopicInfo = async () => { + try { + const res = await getTopicByFqn( + entityDetails.fullyQualifiedName ?? '', + '' + ); + + const { partitions } = res; + + setTopicDetails({ ...entityDetails, partitions }); + } catch { + showErrorToast( + t('server.entity-details-fetch-error', { + entityType: t('label.topic-lowercase'), + entityName: entityDetails.name, + }) + ); + } + }; + + useEffect(() => { + fetchExtraTopicInfo(); }, [entityDetails]); return ( @@ -56,14 +95,19 @@ function TopicSummary({ entityDetails }: TopicSummaryProps) { const value = topicConfig[fieldName as keyof TopicConfigObjectInterface]; + const fieldValue = isArray(value) ? value.join(', ') : value; + return ( - + {fieldName} - - {isArray(value) ? value.join(', ') : value} + + {fieldValue ? fieldValue : '-'} @@ -73,21 +117,22 @@ function TopicSummary({ entityDetails }: TopicSummaryProps) {
- + - + {t('label.schema')} - {entityDetails.messageSchema?.schemaText ? ( - + {entityDetails.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 new file mode 100644 index 00000000000..76ec108c031 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/TopicSummary/TopicSummary.test.tsx @@ -0,0 +1,111 @@ +/* + * 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 { act, render, screen } from '@testing-library/react'; +import React from 'react'; +import { getTopicByFqn } from 'rest/topicsAPI'; +import { mockTopicEntityDetails } 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() + .mockImplementation(() =>
SummaryList
) +); + +jest.mock('rest/topicsAPI', () => ({ + getTopicByFqn: jest.fn().mockImplementation(() => ({ partitions: 128 })), +})); + +describe('TopicSummary component tests', () => { + it('Component should render properly', async () => { + await act(async () => { + render(); + }); + + const topicTitle = screen.getByTestId('TableDataCardTitle'); + const partitionsLabel = screen.getByTestId('Partitions-label'); + const replicationFactorLabel = screen.getByTestId( + 'Replication Factor-label' + ); + const retentionSizeLabel = screen.getByTestId('Retention Size-label'); + const cleanUpPoliciesLabel = screen.getByTestId('CleanUp Policies-label'); + const maxMessageSizeLabel = screen.getByTestId('Max Message Size-label'); + const partitionsValue = screen.getByTestId('Partitions-value'); + const replicationFactorValue = screen.getByTestId( + 'Replication Factor-value' + ); + const retentionSizeValue = screen.getByTestId('Retention Size-value'); + const cleanUpPoliciesValue = screen.getByTestId('CleanUp Policies-value'); + const maxMessageSizeValue = screen.getByTestId('Max Message Size-value'); + 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(replicationFactorValue).toContainHTML('4'); + expect(retentionSizeValue).toContainHTML('1018.83 MB'); + expect(cleanUpPoliciesValue).toContainHTML('delete'); + expect(maxMessageSizeValue).toContainHTML('208 Bytes'); + expect(schemaHeader).toBeInTheDocument(); + expect(summaryList).toBeInTheDocument(); + }); + + it('No data message should be shown in case not schemaFields are available in topic details', async () => { + await act(async () => { + render( + + ); + }); + + const summaryList = screen.queryByTestId('SummaryList'); + const noDataMessage = screen.queryByTestId('no-data-message'); + + expect(summaryList).toBeNull(); + expect(noDataMessage).toBeInTheDocument(); + }); + + it('In case any topic field is not present, "-" should be displayed in place of value', async () => { + (getTopicByFqn as jest.Mock).mockImplementationOnce(() => Promise.reject()); + await act(async () => { + render(); + }); + + const partitionsValue = screen.getByTestId('Partitions-value'); + + expect(partitionsValue).toContainHTML('-'); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/mocks/DashboardSummary.mock.ts b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/mocks/DashboardSummary.mock.ts new file mode 100644 index 00000000000..a3c324c69f4 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/mocks/DashboardSummary.mock.ts @@ -0,0 +1,99 @@ +/* + * 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 { + Dashboard, + DashboardServiceType, +} from '../../../../generated/entity/data/dashboard'; + +const mockDate = new Date('2023-01-03'); + +export const mockDashboardEntityDetails: Dashboard = { + id: '2edaff89-b1d4-47b6-a081-d72f08e1def9', + name: 'deck.gl Demo', + displayName: 'deck.gl Demo', + fullyQualifiedName: 'sample_superset.10', + description: '', + version: 0.1, + updatedAt: 1672627828951, + updatedBy: 'admin', + dashboardUrl: 'http://localhost:808/superset/dashboard/deck/', + charts: [ + { + id: 'eba9c260-4036-4c57-92fe-6c6e3d703bda', + type: 'chart', + name: '127', + fullyQualifiedName: 'sample_superset.127', + description: '', + displayName: 'Are you an ethnic minority in your city?', + deleted: false, + href: 'http://openmetadata-server:8585/api/v1/charts/eba9c260-4036-4c57-92fe-6c6e3d703bda', + }, + ], + href: 'http://openmetadata-server:8585/api/v1/dashboards/2edaff89-b1d4-47b6-a081-d72f08e1def9', + followers: [], + service: { + id: '38ae6d66-7086-4e00-b2d6-cabd2b951993', + type: 'dashboardService', + name: 'sample_superset', + fullyQualifiedName: 'sample_superset', + deleted: false, + href: 'http://openmetadata-server:8585/api/v1/services/dashboardServices/38ae6d66-7086-4e00-b2d6-cabd2b951993', + }, + serviceType: DashboardServiceType.Superset, + usageSummary: { + dailyStats: { + count: 0, + percentileRank: 0, + }, + weeklyStats: { + count: 0, + percentileRank: 0, + }, + monthlyStats: { + count: 0, + percentileRank: 0, + }, + date: mockDate, + }, + deleted: false, + tags: [], +}; + +export const mockFetchChartsResponse = [ + { + id: 'eba9c260-4036-4c57-92fe-6c6e3d703bda', + name: '127', + displayName: 'Are you an ethnic minority in your city?', + fullyQualifiedName: 'sample_superset.127', + description: '', + version: 0.1, + updatedAt: 1672627828742, + updatedBy: 'admin', + chartType: 'Other', + chartUrl: + 'http://localhost:8088/superset/explore/?form_data=%7B%22slice_id%22%3A%20127%7D', + href: 'http://localhost:8585/api/v1/charts/eba9c260-4036-4c57-92fe-6c6e3d703bda', + tags: [], + service: { + id: '38ae6d66-7086-4e00-b2d6-cabd2b951993', + type: 'dashboardService', + name: 'sample_superset', + fullyQualifiedName: 'sample_superset', + deleted: false, + href: 'http://localhost:8585/api/v1/services/dashboardServices/38ae6d66-7086-4e00-b2d6-cabd2b951993', + }, + serviceType: 'Superset', + deleted: false, + }, +]; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/mocks/MlModelSummary.mock.ts b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/mocks/MlModelSummary.mock.ts new file mode 100644 index 00000000000..17c0be6e99c --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/mocks/MlModelSummary.mock.ts @@ -0,0 +1,146 @@ +/* + * 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 { + FeatureSourceDataType, + FeatureType, + Mlmodel, +} from '../../../../generated/entity/data/mlmodel'; + +export const mockMlModelEntityDetails: Mlmodel = { + id: 'e42a5d43-36fd-4636-ae89-5bcc6a61542e', + name: 'eta_predictions', + displayName: 'ETA Predictions', + fullyQualifiedName: 'mlflow_svc.eta_predictions', + description: 'ETA Predictions Model', + version: 0.1, + updatedAt: 1672627829904, + updatedBy: 'admin', + algorithm: 'Neural Network', + mlFeatures: [ + { + name: 'sales', + dataType: FeatureType.Numerical, + description: 'Sales amount', + fullyQualifiedName: 'mlflow_svc.eta_predictions.sales', + featureSources: [ + { + name: 'gross_sales', + dataType: FeatureSourceDataType.Integer, + fullyQualifiedName: + 'sample_data.ecommerce_db.shopify.fact_sale.gross_sales', + dataSource: { + id: '148e01f8-817a-4094-aa39-970746e3427e', + type: 'table', + fullyQualifiedName: 'sample_data.ecommerce_db.shopify.fact_sale', + description: + 'The fact table captures the value of products sold or returned.', + href: 'http://openmetadata-server:8585/api/v1/tables/148e01f8-817a-4094-aa39-970746e3427e', + }, + }, + ], + }, + { + name: 'persona', + dataType: FeatureType.Categorical, + description: 'type of buyer', + fullyQualifiedName: 'mlflow_svc.eta_predictions.persona', + featureSources: [ + { + name: 'membership', + dataType: FeatureSourceDataType.String, + fullyQualifiedName: + 'sample_data.ecommerce_db.shopify.raw_customer.membership', + dataSource: { + id: 'e2e27b89-9eda-441f-afe4-3c9b780e9e15', + type: 'table', + fullyQualifiedName: 'sample_data.ecommerce_db.shopify.raw_customer', + description: + 'This is a raw customers table as represented in our online DB. ', + href: 'http://openmetadata-server:8585/api/v1/tables/e2e27b89-9eda-441f-afe4-3c9b780e9e15', + }, + }, + { + name: 'platform', + dataType: FeatureSourceDataType.String, + fullyQualifiedName: + 'sample_data.ecommerce_db.shopify.raw_customer.platform', + dataSource: { + id: 'e2e27b89-9eda-441f-afe4-3c9b780e9e15', + type: 'table', + fullyQualifiedName: 'sample_data.ecommerce_db.shopify.raw_customer', + description: + 'This is a raw customers table as represented in our online DB. This contains personal.', + href: 'http://openmetadata-server:8585/api/v1/tables/e2e27b89-9eda-441f-afe4-3c9b780e9e15', + }, + }, + ], + featureAlgorithm: 'PCA', + }, + ], + mlHyperParameters: [ + { + name: 'regularisation', + value: '0.5', + }, + { + name: 'random', + value: 'hello', + }, + ], + dashboard: { + name: 'DashboardName', + id: '4352345234534538992643452345', + type: '', + }, + target: 'ETA_time', + mlStore: { + storage: 's3://path-to-pickle', + imageRepository: 'https://docker.hub.com/image', + }, + service: { + name: 'MLFlow', + id: '43523452345345325423452345', + type: '', + }, + server: 'http://my-server.ai', + tags: [], + followers: [], + href: 'http://openmetadata-server:8585/api/v1/mlmodels/e42a5d43-36fd-4636-ae89-5bcc6a61542e', + deleted: false, +}; + +export const mockMlModelEntityDetails1: Mlmodel = { + id: 'b849cc70-ceda-4f2a-8de2-022a5c7f78a6', + name: 'forecast_sales', + displayName: 'Sales Forecast Predictions', + fullyQualifiedName: 'mlflow_svc.forecast_sales', + description: 'Sales Forecast Predictions Model', + version: 0.1, + updatedAt: 1672627829947, + updatedBy: 'admin', + algorithm: 'Time Series', + mlFeatures: [], + mlHyperParameters: [], + target: '', + server: '', + service: { + name: 'MLFlow', + id: '43523452345345325423452345', + type: '', + }, + tags: [], + followers: [], + href: 'http://openmetadata-server:8585/api/v1/mlmodels/b849cc70-ceda-4f2a-8de2-022a5c7f78a6', + deleted: false, +}; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/mocks/PipelineSummary.mock.ts b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/mocks/PipelineSummary.mock.ts new file mode 100644 index 00000000000..b032420789d --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/mocks/PipelineSummary.mock.ts @@ -0,0 +1,59 @@ +/* + * 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 { Pipeline } from '../../../../generated/entity/data/pipeline'; + +export const mockPipelineEntityDetails: Pipeline = { + id: 'b35f7a53-16a9-4ed1-9223-801c3d75674f', + name: 'dim_address_etl', + displayName: 'dim_address etl', + fullyQualifiedName: 'sample_airflow.dim_address_etl', + description: 'dim_address ETL pipeline', + version: 0.1, + updatedAt: 1672627829327, + updatedBy: 'admin', + pipelineUrl: 'http://localhost:8080/tree?dag_id=dim_address_etl', + tasks: [ + { + name: 'dim_address_task', + displayName: 'dim_address Task', + description: + 'Airflow operator to perform ETL and generate dim_address table', + taskUrl: + 'http://localhost:8080/taskinstance/list/?flt1_dag_id_equals=dim_address_task', + downstreamTasks: ['assert_table_exists'], + taskType: 'PrestoOperator', + }, + { + name: 'assert_table_exists', + displayName: 'Assert Table Exists', + description: 'Assert if a table exists', + taskUrl: + 'http://localhost:8080/taskinstance/list/?flt1_dag_id_equals=assert_table_exists', + downstreamTasks: [], + taskType: 'HiveOperator', + }, + ], + deleted: false, + href: 'http://openmetadata-server:8585/api/v1/pipelines/b35f7a53-16a9-4ed1-9223-801c3d75674f', + followers: [], + tags: [], + service: { + id: 'd1c5f7b4-dc61-4336-a4b1-a27e0b97d791', + type: 'pipelineService', + name: 'sample_airflow', + fullyQualifiedName: 'sample_airflow', + deleted: false, + href: 'http://openmetadata-server:8585/api/v1/services/pipelineServices/d1c5f7b4-dc61-4336-a4b1-a27e0b97d791', + }, +}; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/mocks/SummaryList.mock.ts b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/mocks/SummaryList.mock.ts new file mode 100644 index 00000000000..2ca592d78f0 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/mocks/SummaryList.mock.ts @@ -0,0 +1,64 @@ +/* + * 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. + */ + +export const mockFormattedEntityData = [ + { + children: [], + constraint: undefined, + description: 'Description for name1', + name: 'name1', + tags: [], + title: 'Title1', + type: 'ARRAY', + }, + { + children: [], + constraint: undefined, + description: 'Description for name2', + name: 'name2', + tags: [], + title: 'Title2', + type: 'OBJECT', + }, +]; + +export const mockFormattedEntityDataWithChildren = [ + { + children: [ + { + children: [], + constraint: undefined, + description: 'Description for child1', + name: 'child1', + tags: [], + title: 'ChildTitle2', + type: 'OBJECT', + }, + ], + constraint: undefined, + description: 'Description for name1', + name: 'name1', + tags: [], + title: 'Title1', + type: 'ARRAY', + }, + { + children: [], + constraint: undefined, + description: 'Description for name2', + name: 'name2', + tags: [], + title: 'Title2', + type: 'OBJECT', + }, +]; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/mocks/SummaryListItems.mock.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/mocks/SummaryListItems.mock.tsx new file mode 100644 index 00000000000..76998bf5620 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/mocks/SummaryListItems.mock.tsx @@ -0,0 +1,69 @@ +/* + * 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 React from 'react'; +import { Constraint } from '../../../../generated/entity/data/table'; +import { + LabelType, + State, + TagSource, +} from '../../../../generated/type/tagLabel'; + +export const mockEntityDetails = { + children: [], + constraint: undefined, + description: 'Description for shipping_address', + name: 'shipping_address', + tags: [], + title:
Title
, + type: 'ARRAY', +}; + +export const mockEntityDetailsWithTagsAndAlgorithm = { + children: [], + algorithm: 'The Algo', + constraint: undefined, + description: undefined, + name: 'shipping_address', + tags: [ + { + tagFQN: 'PersonalData.SpecialCategory', + labelType: LabelType.Manual, + description: 'Test Description', + source: TagSource.Tag, + state: State.Confirmed, + }, + ], + title:
Title
, + type: 'ARRAY', +}; + +export const mockEntityDetailsWithoutDescription = { + children: [], + constraint: undefined, + description: undefined, + name: 'shipping_address', + tags: [], + title:
Title
, + type: 'ARRAY', +}; + +export const mockEntityDetailsWithConstraint = { + children: [], + constraint: Constraint.PrimaryKey, + description: undefined, + name: 'shipping_address', + tags: [], + title:
Title
, + type: 'ARRAY', +}; 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 new file mode 100644 index 00000000000..8dac0bcc098 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/mocks/TableSummary.mock.ts @@ -0,0 +1,101 @@ +/* + * 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 { + DatabaseServiceType, + DataType, + LabelType, + State, + Table, + TagSource, +} from '../../../../generated/entity/data/table'; + +const mockDate = new Date('2023-01-03'); + +export const mockTableEntityDetails: Table = { + id: '8dd1f238-6ba0-46c6-a091-7db81f2a6bed', + name: 'dim.api/client', + fullyQualifiedName: 'sample_data.ecommerce_db.shopify."dim.api/client"', + description: + 'This dimension table contains a row for each channel or app that your customers use to create orders. ', + displayName: 'dim.api/client', + version: 0.2, + updatedAt: 1672668265493, + updatedBy: 'admin', + href: 'http://openmetadata-server:8585/api/v1/tables/8dd1f238-6ba0-46c6-a091-7db81f2a6bed', + columns: [ + { + name: 'api_client_id', + dataType: DataType.Numeric, + dataTypeDisplay: 'numeric', + description: + 'ID of the API client that called the Shopify API. For example, the ID for the online store is 580111.', + fullyQualifiedName: + 'sample_data.ecommerce_db.shopify."dim.api/client".api_client_id', + tags: [ + { + tagFQN: 'PersonalData.SpecialCategory', + description: + 'GDPR special category data is personal information of data subjects that is especially sensitive.', + source: TagSource.Tag, + labelType: LabelType.Manual, + state: State.Confirmed, + }, + ], + ordinalPosition: 1, + }, + { + name: 'title', + dataType: DataType.Varchar, + dataLength: 100, + dataTypeDisplay: 'varchar', + description: + 'Full name of the app or channel. For example, Point of Sale, Online Store.', + fullyQualifiedName: + 'sample_data.ecommerce_db.shopify."dim.api/client".title', + tags: [], + ordinalPosition: 2, + }, + ], + deleted: false, + serviceType: DatabaseServiceType.BigQuery, + tags: [ + { + tagFQN: 'PersonalData.SpecialCategory', + description: + 'GDPR special category data is personal information of data subjects that is especially sensitive.', + source: TagSource.Tag, + labelType: LabelType.Manual, + state: State.Confirmed, + }, + ], + tableQueries: [ + { + query: + 'select cust.customer_id, fact_order.order_id from dim_customer cust join fact_order on', + users: [], + vote: 1, + checksum: 'ff727cf70d5a7a9810704532f3571b82', + queryDate: mockDate, + }, + { + query: + 'select sale.sale_id, cust.customer_id, fact_order.order_ir from shopify.', + users: [], + vote: 1, + checksum: 'e14e02c387dd8482d10c4ec7d3d4c69a', + queryDate: mockDate, + }, + ], + followers: [], +}; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/mocks/TopicSummary.mock.ts b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/mocks/TopicSummary.mock.ts new file mode 100644 index 00000000000..a2acb9c223b --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/mocks/TopicSummary.mock.ts @@ -0,0 +1,100 @@ +/* + * 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 { + CleanupPolicy, + DataTypeTopic, + SchemaType, + Topic, +} from '../../../../generated/entity/data/topic'; + +export const mockTopicEntityDetails: Topic = { + id: '67e1dbbb-054c-4833-ba28-f95d71f0826f', + name: 'product_events', + displayName: 'product_events', + fullyQualifiedName: 'sample_kafka.product_events', + description: + 'Kafka topic to capture the product events. This topic will get updates on products decription, price etc.', + version: 0.1, + updatedAt: 1672627828429, + updatedBy: 'admin', + href: 'http://openmetadata-server:8585/api/v1/topics/67e1dbbb-054c-4833-ba28-f95d71f0826f', + deleted: false, + service: { + id: '5d6f73f0-1811-49c8-8d1d-7a478ffd8177', + type: 'messagingService', + name: 'sample_kafka', + fullyQualifiedName: 'sample_kafka', + deleted: false, + href: 'http://openmetadata-server:8585/api/v1/services/messagingServices/5d6f73f0-1811-49c8-8d1d-7a478ffd8177', + }, + messageSchema: { + schemaText: + '{"namespace":"openmetadata.kafka","type":"record","name":"Product","fields":[{"name":"product_id","type":"int"}]}', + schemaType: SchemaType.Avro, + schemaFields: [ + { + name: 'Product', + dataType: DataTypeTopic.Record, + fullyQualifiedName: 'sample_kafka.product_events.Product', + tags: [], + children: [ + { + name: 'product_id', + dataType: DataTypeTopic.Int, + fullyQualifiedName: + 'sample_kafka.product_events.Product.product_id', + tags: [], + }, + { + name: 'title', + dataType: DataTypeTopic.String, + fullyQualifiedName: 'sample_kafka.product_events.Product.title', + tags: [], + }, + { + name: 'price', + dataType: DataTypeTopic.Double, + fullyQualifiedName: 'sample_kafka.product_events.Product.price', + tags: [], + }, + { + name: 'sku', + dataType: DataTypeTopic.String, + fullyQualifiedName: 'sample_kafka.product_events.Product.sku', + tags: [], + }, + { + name: 'barcode', + dataType: DataTypeTopic.String, + fullyQualifiedName: 'sample_kafka.product_events.Product.barcode', + tags: [], + }, + { + name: 'shop_id', + dataType: DataTypeTopic.Int, + fullyQualifiedName: 'sample_kafka.product_events.Product.shop_id', + tags: [], + }, + ], + }, + ], + }, + partitions: 0, + cleanupPolicies: [CleanupPolicy.Delete], + replicationFactor: 4, + maximumMessageSize: 208, + retentionSize: 1068320655, + tags: [], + followers: [], +}; 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 a5e9479a96d..e9b9ee15645 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 @@ -23,6 +23,7 @@ import { Thread, ThreadType } from '../../generated/entity/feed/thread'; import { EntityLineage } from '../../generated/type/entityLineage'; import { EntityReference } from '../../generated/type/entityReference'; import { Paging } from '../../generated/type/paging'; +import { SchemaType } from '../../generated/type/schema'; import { TagLabel } from '../../generated/type/tagLabel'; import { EntityFieldThreadCount, @@ -102,4 +103,5 @@ export interface TopicConfigObjectInterface { 'Retention Size'?: number; 'CleanUp Policies'?: CleanupPolicy[]; 'Max Message Size'?: number; + 'Schema Type'?: SchemaType; } diff --git a/openmetadata-ui/src/main/resources/ui/src/enums/EntitySummary.enum.ts b/openmetadata-ui/src/main/resources/ui/src/enums/EntitySummary.enum.ts new file mode 100644 index 00000000000..500db86d820 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/enums/EntitySummary.enum.ts @@ -0,0 +1,20 @@ +/* + * 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. + */ + +export enum SummaryEntityType { + COLUMN = 'column', + CHART = 'chart', + TASK = 'task', + MLFEATURE = 'mlFeature', + SCHEMAFIELD = 'schemaField', +} diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/EntitySummaryPanelUtils.test.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/EntitySummaryPanelUtils.test.tsx new file mode 100644 index 00000000000..92b51b64fa4 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/utils/EntitySummaryPanelUtils.test.tsx @@ -0,0 +1,61 @@ +/* + * 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 { SummaryEntityType } from '../enums/EntitySummary.enum'; +import { Column } from '../generated/entity/data/table'; +import { getFormattedEntityData } from './EntitySummaryPanelUtils'; +import { + mockEntityDataWithNesting, + mockEntityDataWithNestingResponse, + mockEntityDataWithoutNesting, + mockEntityDataWithoutNestingResponse, + mockInvalidDataResponse, +} from './mocks/EntitySummaryPanelUtils.mock'; + +describe('EntitySummaryPanelUtils tests', () => { + it('getFormattedEntityData should return formatted data properly for table columns data without nesting', () => { + const resultFormattedData = getFormattedEntityData( + SummaryEntityType.COLUMN, + mockEntityDataWithoutNesting + ); + + expect(resultFormattedData).toEqual(mockEntityDataWithoutNestingResponse); + }); + + it('getFormattedEntityData should return formatted data properly for topic fields data with nesting', () => { + const resultFormattedData = getFormattedEntityData( + SummaryEntityType.COLUMN, + mockEntityDataWithNesting + ); + + expect(resultFormattedData).toEqual(mockEntityDataWithNestingResponse); + }); + + it('getFormattedEntityData should return empty array in case entityType is given other than from type SummaryEntityType', () => { + const resultFormattedData = getFormattedEntityData( + 'otherType' as SummaryEntityType, + mockEntityDataWithNesting + ); + + expect(resultFormattedData).toEqual([]); + }); + + it('getFormattedEntityData should not throw error if entityDetails sent does not have fields present', () => { + const resultFormattedData = getFormattedEntityData( + SummaryEntityType.COLUMN, + [{}] as Column[] + ); + + expect(resultFormattedData).toEqual(mockInvalidDataResponse); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/EntitySummaryPanelUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/EntitySummaryPanelUtils.tsx new file mode 100644 index 00000000000..55713089d9a --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/utils/EntitySummaryPanelUtils.tsx @@ -0,0 +1,111 @@ +/* + * 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 { Space, Typography } from 'antd'; +import { isEmpty } from 'lodash'; +import React from 'react'; +import { Link } from 'react-router-dom'; +import { BasicEntityInfo } from '../components/Explore/EntitySummaryPanel/SummaryList/SummaryList.interface'; +import { SummaryEntityType } from '../enums/EntitySummary.enum'; +import { Chart } from '../generated/entity/data/chart'; +import { MlFeature } from '../generated/entity/data/mlmodel'; +import { Task } from '../generated/entity/data/pipeline'; +import { Column } from '../generated/entity/data/table'; +import { Field } from '../generated/entity/data/topic'; +import { getEntityName } from './CommonUtils'; +import SVGIcons from './SvgUtils'; + +const { Text } = Typography; + +export const getFormattedEntityData = ( + entityType: SummaryEntityType, + entityInfo?: Column[] | Field[] | Chart[] | Task[] | MlFeature[] +): BasicEntityInfo[] => { + if (isEmpty(entityInfo)) { + return []; + } + switch (entityType) { + case SummaryEntityType.COLUMN: { + return (entityInfo as Column[]).map((column) => ({ + name: column.name, + title: {column.name}, + type: column.dataType, + tags: column.tags, + description: column.description, + constraint: column.constraint, + children: getFormattedEntityData( + SummaryEntityType.COLUMN, + column.children + ), + })); + } + case SummaryEntityType.CHART: { + return (entityInfo as Chart[]).map((chart) => ({ + name: chart.name, + title: ( + + + {getEntityName(chart)} + + + + ), + type: chart.chartType, + tags: chart.tags, + description: chart.description, + })); + } + case SummaryEntityType.TASK: { + return (entityInfo as Task[]).map((task) => ({ + name: task.name, + title: ( + + + {task.name} + + + + ), + type: task.taskType, + tags: task.tags, + description: task.description, + })); + } + case SummaryEntityType.MLFEATURE: { + return (entityInfo as MlFeature[]).map((feature) => ({ + algorithm: feature.featureAlgorithm, + name: feature.name || '--', + title: {feature.name}, + type: feature.dataType, + tags: feature.tags, + description: feature.description, + })); + } + case SummaryEntityType.SCHEMAFIELD: { + return (entityInfo as Field[]).map((field) => ({ + name: field.name, + title: {field.name}, + type: field.dataType, + description: field.description, + tags: field.tags, + children: getFormattedEntityData( + SummaryEntityType.SCHEMAFIELD, + field.children + ), + })); + } + default: { + return []; + } + } +}; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/TopicDetailsUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/TopicDetailsUtils.ts index 3d9f6400f91..cda0f31375b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/TopicDetailsUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/TopicDetailsUtils.ts @@ -89,5 +89,6 @@ export const getConfigObject = ( 'Retention Size': topicDetails.retentionSize, 'CleanUp Policies': topicDetails.cleanupPolicies, 'Max Message Size': topicDetails.maximumMessageSize, + 'Schema Type': topicDetails.messageSchema?.schemaType, }; }; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/mocks/EntitySummaryPanelUtils.mock.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/mocks/EntitySummaryPanelUtils.mock.tsx new file mode 100644 index 00000000000..a2362610a6e --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/utils/mocks/EntitySummaryPanelUtils.mock.tsx @@ -0,0 +1,241 @@ +/* + * 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 { Typography } from 'antd'; +import React from 'react'; +import { + Column, + DataType, + LabelType, + State, + TagSource, +} from '../../generated/entity/data/table'; +import { DataTypeTopic, Field } from '../../generated/entity/data/topic'; + +const { Text } = Typography; + +export const mockEntityDataWithoutNesting: Column[] = [ + { + name: 'api_client_id', + dataType: DataType.Numeric, + dataTypeDisplay: 'numeric', + description: + 'ID of the API client that called the Shopify API. For example, the ID for the online store is 580111.', + fullyQualifiedName: + 'sample_data.ecommerce_db.shopify."dim.api/client".api_client_id', + tags: [ + { + tagFQN: 'PersonalData.SpecialCategory', + description: + 'GDPR special category data is personal information of data subjects that is especially sensitive.', + source: TagSource.Tag, + labelType: LabelType.Manual, + state: State.Confirmed, + }, + ], + ordinalPosition: 1, + }, + { + name: 'title', + dataType: DataType.Varchar, + dataLength: 100, + dataTypeDisplay: 'varchar', + description: + 'Full name of the app or channel. For example, Point of Sale, Online Store.', + fullyQualifiedName: + 'sample_data.ecommerce_db.shopify."dim.api/client".title', + tags: [], + ordinalPosition: 2, + }, +]; + +export const mockEntityDataWithoutNestingResponse = [ + { + children: [], + constraint: undefined, + description: + 'ID of the API client that called the Shopify API. For example, the ID for the online store is 580111.', + name: 'api_client_id', + tags: [ + { + tagFQN: 'PersonalData.SpecialCategory', + description: + 'GDPR special category data is personal information of data subjects that is especially sensitive.', + source: 'Tag', + labelType: 'Manual', + state: 'Confirmed', + }, + ], + title: api_client_id, + type: 'NUMERIC', + }, + { + children: [], + constraint: undefined, + description: + 'Full name of the app or channel. For example, Point of Sale, Online Store.', + name: 'title', + tags: [], + title: title, + type: 'VARCHAR', + }, +]; + +export const mockEntityDataWithNesting: Field[] = [ + { + name: 'Customer', + dataType: DataTypeTopic.Record, + fullyQualifiedName: 'sample_kafka.customer_events.Customer', + tags: [], + children: [ + { + name: 'id', + dataType: DataTypeTopic.String, + fullyQualifiedName: 'sample_kafka.customer_events.Customer.id', + tags: [], + }, + { + name: 'first_name', + dataType: DataTypeTopic.String, + fullyQualifiedName: 'sample_kafka.customer_events.Customer.first_name', + tags: [], + }, + { + name: 'last_name', + dataType: DataTypeTopic.String, + fullyQualifiedName: 'sample_kafka.customer_events.Customer.last_name', + tags: [], + }, + { + name: 'email', + dataType: DataTypeTopic.String, + fullyQualifiedName: 'sample_kafka.customer_events.Customer.email', + tags: [], + }, + { + name: 'address_line_1', + dataType: DataTypeTopic.String, + fullyQualifiedName: + 'sample_kafka.customer_events.Customer.address_line_1', + tags: [], + }, + { + name: 'address_line_2', + dataType: DataTypeTopic.String, + fullyQualifiedName: + 'sample_kafka.customer_events.Customer.address_line_2', + tags: [], + }, + { + name: 'post_code', + dataType: DataTypeTopic.String, + fullyQualifiedName: 'sample_kafka.customer_events.Customer.post_code', + tags: [], + }, + { + name: 'country', + dataType: DataTypeTopic.String, + fullyQualifiedName: 'sample_kafka.customer_events.Customer.country', + tags: [], + }, + ], + }, +]; + +export const mockEntityDataWithNestingResponse = [ + { + children: [ + { + children: [], + description: undefined, + name: 'id', + tags: [], + title: id, + type: 'STRING', + }, + { + children: [], + description: undefined, + name: 'first_name', + tags: [], + title: first_name, + type: 'STRING', + }, + { + children: [], + description: undefined, + name: 'last_name', + tags: [], + title: last_name, + type: 'STRING', + }, + { + children: [], + description: undefined, + name: 'email', + tags: [], + title: email, + type: 'STRING', + }, + { + children: [], + description: undefined, + name: 'address_line_1', + tags: [], + title: address_line_1, + type: 'STRING', + }, + { + children: [], + description: undefined, + name: 'address_line_2', + tags: [], + title: address_line_2, + type: 'STRING', + }, + { + children: [], + description: undefined, + name: 'post_code', + tags: [], + title: post_code, + type: 'STRING', + }, + { + children: [], + description: undefined, + name: 'country', + tags: [], + title: country, + type: 'STRING', + }, + ], + description: undefined, + name: 'Customer', + tags: [], + title: Customer, + type: 'RECORD', + }, +]; + +export const mockInvalidDataResponse = [ + { + children: [], + constraints: undefined, + description: undefined, + name: undefined, + tags: undefined, + title: , + type: undefined, + }, +];