From afa93cbba2a65c921d90a631311ddaa5e0880d97 Mon Sep 17 00:00:00 2001 From: Sachin Chaurasiya Date: Mon, 12 Dec 2022 20:03:21 +0530 Subject: [PATCH] Fix (#9238) : Dashboard owner and tags not appearing on dashboard list (#9250) * Fix (#9238) : Dashboard owner and tags not appearing on dashboard list * test: add unit tests --- .../ui/src/pages/service/index.test.tsx | 222 ++++++++++++++++-- .../resources/ui/src/pages/service/index.tsx | 41 +++- .../pages/service/mocks/servicePage.mock.ts | 160 +++++++++++++ 3 files changed, 388 insertions(+), 35 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/service/index.test.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/service/index.test.tsx index cec955d7afa..b1b34aeee35 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/service/index.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/service/index.test.tsx @@ -12,17 +12,31 @@ */ import { - findAllByTestId, + act, findByTestId, findByText, - queryByText, + queryByTestId, render, + screen, } from '@testing-library/react'; import React from 'react'; import { MemoryRouter } from 'react-router-dom'; -import { act } from 'react-test-renderer'; +import { getCurrentServiceTab } from '../../utils/ServiceUtils'; + +let mockParams = { + serviceFQN: 'bigquery_gcp', + serviceType: 'BigQuery', + serviceCategory: 'databaseServices', + tab: 'databases', +}; + import ServicePage from './index'; -import { mockData, mockDatabase, mockTabs } from './mocks/servicePage.mock'; +import { + DASHBOARD_DATA, + mockData, + mockDatabase, + mockTabs, +} from './mocks/servicePage.mock'; jest.mock('../../utils/PermissionsUtils', () => ({ checkPermission: jest.fn().mockReturnValue(true), @@ -94,7 +108,15 @@ jest.mock('../../axiosAPIs/topicsAPI', () => ({ })); jest.mock('../../axiosAPIs/dashboardAPI', () => ({ - getDashboards: jest.fn().mockImplementation(() => Promise.resolve()), + getDashboards: jest.fn().mockImplementation(() => + Promise.resolve({ + data: DASHBOARD_DATA, + paging: { + after: null, + before: null, + }, + }) + ), })); jest.mock('../../axiosAPIs/serviceAPI', () => ({ @@ -125,12 +147,7 @@ jest.mock('react-router-dom', () => ({ {children} )), useHistory: jest.fn(), - useParams: jest.fn().mockReturnValue({ - serviceFQN: 'bigquery_gcp', - serviceType: 'BigQuery', - serviceCategory: 'databaseServices', - tab: 'databases', - }), + useParams: jest.fn().mockImplementation(() => mockParams), })); jest.mock('../../components/containers/PageContainer', () => { @@ -142,7 +159,7 @@ jest.mock('../../components/containers/PageContainer', () => { }); jest.mock('../../utils/ServiceUtils', () => ({ - getCurrentServiceTab: jest.fn().mockReturnValue(1), + getCurrentServiceTab: jest.fn().mockImplementation(() => 1), getIsIngestionEnable: jest.fn().mockReturnValue(true), servicePageTabs: jest.fn().mockReturnValue([ { @@ -150,6 +167,14 @@ jest.mock('../../utils/ServiceUtils', () => ({ path: 'databases', field: 'databases', }, + { + name: 'Ingestions', + path: 'ingestions', + }, + { + name: 'Connection', + path: 'connection', + }, ]), getServiceRouteFromServiceType: jest.fn().mockReturnValue('/database'), getServiceCategoryFromType: jest.fn().mockReturnValue('databaseServices'), @@ -203,6 +228,34 @@ jest.mock( return jest.fn().mockReturnValue(
ManageButton
); } ); +jest.mock('../../components/Ingestion/Ingestion.component', () => { + return jest + .fn() + .mockReturnValue(
Ingestion
); +}); + +jest.mock( + '../../components/ServiceConnectionDetails/ServiceConnectionDetails.component', + () => { + return jest + .fn() + .mockReturnValue( +
ServiceConnectionDetails
+ ); + } +); + +jest.mock('../../components/tags-viewer/tags-viewer', () => { + return jest + .fn() + .mockReturnValue(
Tag Viewer
); +}); + +jest.mock('../../components/common/ProfilePicture/ProfilePicture', () => { + return jest.fn().mockImplementation(({ name }) => { + return
{name}
; + }); +}); jest.mock('../../utils/TableUtils', () => ({ getEntityLink: jest.fn(), @@ -228,33 +281,158 @@ describe('Test ServicePage Component', () => { ); const description = await findByText(container, /Description_component/i); const tabPane = await findByText(container, /TabsPane_component/i); + const tableContainer = await findByTestId(container, 'table-container'); expect(servicePage).toBeInTheDocument(); expect(titleBreadcrumb).toBeInTheDocument(); expect(descriptionContainer).toBeInTheDocument(); expect(description).toBeInTheDocument(); expect(tabPane).toBeInTheDocument(); + expect(tableContainer).toBeInTheDocument(); }); }); - it('Table should be visible if data is available', async () => { - const { container } = render(, { + it('Should render the service children table rows', async () => { + render(, { wrapper: MemoryRouter, }); - const tableContainer = await findByTestId(container, 'table-container'); + const tableContainer = await screen.findByTestId('service-children-table'); expect(tableContainer).toBeInTheDocument(); - expect( - queryByText(container, /does not have any databases/i) - ).not.toBeInTheDocument(); + + const rows = await screen.findAllByTestId('row'); + + expect(rows).toHaveLength(1); }); - it('Number of column should be same as data received', async () => { - const { container } = render(, { + it('Should render the owner name and profile pic if child has owner', async () => { + render(, { wrapper: MemoryRouter, }); - const column = await findAllByTestId(container, 'column'); + const tableContainer = await screen.findByTestId('service-children-table'); - expect(column.length).toBe(1); + expect(tableContainer).toBeInTheDocument(); + + const rows = await screen.findAllByTestId('row'); + + const firstRow = rows[0]; + + const ownerData = await findByTestId(firstRow, 'owner-data'); + + expect(ownerData).toBeInTheDocument(); + + // owner profile pic + expect( + await findByTestId(ownerData, 'Compute-profile') + ).toBeInTheDocument(); + + // owner name + expect( + await findByTestId(ownerData, 'Compute-owner-name') + ).toBeInTheDocument(); + }); + + it('Should render the ingestion component', async () => { + mockParams = { ...mockParams, tab: 'ingestions' }; + + (getCurrentServiceTab as jest.Mock).mockImplementationOnce(() => 2); + + await act(async () => { + render(, { + wrapper: MemoryRouter, + }); + }); + + const ingestionContainer = await screen.findByText( + 'Failed to find OpenMetadata - Managed Airflow APIs' + ); + + expect(ingestionContainer).toBeInTheDocument(); + }); + + it('Should render the connection component', async () => { + mockParams = { ...mockParams, tab: 'connection' }; + + (getCurrentServiceTab as jest.Mock).mockImplementationOnce(() => 3); + + await act(async () => { + render(, { + wrapper: MemoryRouter, + }); + }); + + const connectionContainer = await screen.findByTestId( + 'service-connections' + ); + + const editButton = await screen.findByTestId('edit-connection-button'); + const testButton = screen.queryByTestId('test-connection-button'); + + expect(connectionContainer).toBeInTheDocument(); + + expect(editButton).toBeInTheDocument(); + + expect(testButton).not.toBeInTheDocument(); + }); + + it('Should render the dashboards and child components', async () => { + mockParams = { + serviceFQN: 'sample_superset', + serviceType: 'Superset', + serviceCategory: 'dashboardServices', + tab: 'dashboards', + }; + + await act(async () => { + render(, { + wrapper: MemoryRouter, + }); + }); + + const tableContainer = await screen.findByTestId('service-children-table'); + + expect(tableContainer).toBeInTheDocument(); + + const rows = await screen.findAllByTestId('row'); + + expect(rows).toHaveLength(3); + + const firstRow = rows[0]; + const secondRow = rows[1]; + + // first row test + const ownerData = await findByTestId(firstRow, 'owner-data'); + + expect(ownerData).toBeInTheDocument(); + + // owner profile pic + expect( + await findByTestId(ownerData, 'Compute-profile') + ).toBeInTheDocument(); + + // owner name + expect( + await findByTestId(ownerData, 'Compute-owner-name') + ).toBeInTheDocument(); + + const tagContainer = await findByTestId(firstRow, 'record-tags'); + + expect(tagContainer).toBeInTheDocument(); + + // should render tag viewer as it has tags + expect(await findByTestId(tagContainer, 'tag-viewer')).toBeInTheDocument(); + + // second row test + + const noOwnerData = await findByTestId(secondRow, 'no-owner-text'); + + expect(noOwnerData).toBeInTheDocument(); + + const secondRowTagContainer = await findByTestId(secondRow, 'record-tags'); + + expect(secondRowTagContainer).toBeInTheDocument(); + + // should not render tag viewer as it does not have tags + expect(queryByTestId(secondRowTagContainer, 'tag-viewer')).toBeNull(); }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/service/index.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/service/index.tsx index 35c4f814427..b9d8cf7ba57 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/service/index.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/service/index.tsx @@ -11,7 +11,7 @@ * limitations under the License. */ -import { Button, Col, Row, Space, Tooltip } from 'antd'; +import { Button, Col, Row, Space, Tooltip, Typography } from 'antd'; import Table, { ColumnsType } from 'antd/lib/table'; import { AxiosError } from 'axios'; import { t } from 'i18next'; @@ -45,6 +45,7 @@ import EntitySummaryDetails from '../../components/common/EntitySummaryDetails/E import ErrorPlaceHolder from '../../components/common/error-with-placeholder/ErrorPlaceHolder'; import ErrorPlaceHolderIngestion from '../../components/common/error-with-placeholder/ErrorPlaceHolderIngestion'; import NextPrevious from '../../components/common/next-previous/NextPrevious'; +import ProfilePicture from '../../components/common/ProfilePicture/ProfilePicture'; import RichTextEditorPreviewer from '../../components/common/rich-text-editor/RichTextEditorPreviewer'; import TabsPane from '../../components/common/TabsPane/TabsPane'; import TitleBreadcrumb from '../../components/common/title-breadcrumb/title-breadcrumb.component'; @@ -106,7 +107,7 @@ import { IcDeleteColored } from '../../utils/SvgUtils'; import { getEntityLink, getUsagePercentile } from '../../utils/TableUtils'; import { showErrorToast, showSuccessToast } from '../../utils/ToastUtils'; -export type ServicePageData = Database | Topic | Dashboard; +export type ServicePageData = Database | Topic | Dashboard | Mlmodel | Pipeline; const ServicePage: FunctionComponent = () => { const { serviceFQN, serviceType, serviceCategory, tab } = @@ -524,7 +525,7 @@ const ServicePage: FunctionComponent = () => { } }; - const getOptionalTableCells = (data: Database | Topic) => { + const getOptionalTableCells = (data: ServicePageData) => { switch (serviceName) { case ServiceCategory.DATABASE_SERVICES: { const database = data as Database; @@ -927,9 +928,9 @@ const ServicePage: FunctionComponent = () => { title: t('label.description'), dataIndex: 'description', key: 'description', - render: (text: string) => - text?.trim() ? ( - + render: (description: ServicePageData['description']) => + !isUndefined(description) && description.trim() ? ( + ) : ( {t('label.no-description')} @@ -940,16 +941,30 @@ const ServicePage: FunctionComponent = () => { title: t('label.owner'), dataIndex: 'owner', key: 'owner', - render: (record: ServicePageData) => ( -

{record?.owner?.name || '--'}

- ), + render: (owner: ServicePageData['owner']) => + !isUndefined(owner) ? ( + + + + {getEntityName(owner)} + + + ) : ( + -- + ), }, { title: lastColumn, dataIndex: toLower(lastColumn), key: toLower(lastColumn), - render: (record: ServicePageData) => - getOptionalTableCells(record as Database), + render: (_, record: ServicePageData) => ( +
{getOptionalTableCells(record)}
+ ), }, ]; }, []); @@ -1080,10 +1095,10 @@ const ServicePage: FunctionComponent = () => { children, }: { children: React.ReactNode; - }) => {children}, + }) => {children}, }, }} - data-testid="database-table" + data-testid="service-children-table" dataSource={data} pagination={false} rowKey="id" diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/service/mocks/servicePage.mock.ts b/openmetadata-ui/src/main/resources/ui/src/pages/service/mocks/servicePage.mock.ts index 432f6cfd43d..07c10da4473 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/service/mocks/servicePage.mock.ts +++ b/openmetadata-ui/src/main/resources/ui/src/pages/service/mocks/servicePage.mock.ts @@ -38,6 +38,14 @@ export const mockDatabase = { monthlyStats: { count: 0, percentileRank: 0 }, weeklyStats: { count: 0, percentileRank: 0 }, }, + owner: { + id: '0ff251d7-f0ab-4892-96d9-35191f36bf8b', + type: 'team', + name: 'Compute', + fullyQualifiedName: 'Compute', + deleted: false, + href: 'http://sandbox-beta.open-metadata.org/api/v1/teams/0ff251d7-f0ab-4892-96d9-35191f36bf8b', + }, }, ], paging: { @@ -67,3 +75,155 @@ export const mockTabs = [ position: 3, }, ]; + +export const DASHBOARD_DATA = [ + { + id: '04d014be-e24b-40df-913b-c3d3e8c95548', + name: '10', + displayName: 'deck.gl Demo', + fullyQualifiedName: 'sample_superset.10', + description: 'Description.', + version: 0.4, + updatedAt: 1670841493940, + updatedBy: 'sachin.c', + dashboardUrl: 'http://localhost:808/superset/dashboard/deck/', + href: 'http://sandbox-beta.open-metadata.org/api/v1/dashboards/04d014be-e24b-40df-913b-c3d3e8c95548', + owner: { + id: '0ff251d7-f0ab-4892-96d9-35191f36bf8b', + type: 'team', + name: 'Compute', + fullyQualifiedName: 'Compute', + deleted: false, + href: 'http://sandbox-beta.open-metadata.org/api/v1/teams/0ff251d7-f0ab-4892-96d9-35191f36bf8b', + }, + tags: [ + { + tagFQN: 'PersonalData.SpecialCategory', + description: 'GDPR', + source: 'Tag', + labelType: 'Manual', + state: 'Confirmed', + }, + { + tagFQN: 'PII.None', + description: 'Non PII', + source: 'Tag', + labelType: 'Manual', + state: 'Confirmed', + }, + ], + service: { + id: '2b9a4c6a-6dd1-43b1-b73e-434392f7e443', + type: 'dashboardService', + name: 'sample_superset', + fullyQualifiedName: 'sample_superset', + deleted: false, + href: 'http://sandbox-beta.open-metadata.org/api/v1/services/dashboardServices/2b9a4c6a-6dd1-43b1-b73e-434392f7e443', + }, + serviceType: 'Superset', + usageSummary: { + dailyStats: { + count: 0, + percentileRank: 0, + }, + weeklyStats: { + count: 0, + percentileRank: 0, + }, + monthlyStats: { + count: 0, + percentileRank: 0, + }, + date: '2022-12-12', + }, + changeDescription: { + fieldsAdded: [], + fieldsUpdated: [ + { + name: 'description', + oldValue: '', + newValue: 'Description.', + }, + ], + fieldsDeleted: [], + previousVersion: 0.3, + }, + deleted: false, + }, + { + id: '0ab07868-b2da-4879-b29d-0977fac0c360', + name: '11', + displayName: 'FCC New Coder Survey 2018', + fullyQualifiedName: 'sample_superset.11', + description: '', + version: 0.1, + updatedAt: 1668510719495, + updatedBy: 'ingestion-bot', + dashboardUrl: 'http://localhost:808/superset/dashboard/7/', + href: 'http://sandbox-beta.open-metadata.org/api/v1/dashboards/0ab07868-b2da-4879-b29d-0977fac0c360', + tags: [], + service: { + id: '2b9a4c6a-6dd1-43b1-b73e-434392f7e443', + type: 'dashboardService', + name: 'sample_superset', + fullyQualifiedName: 'sample_superset', + deleted: false, + href: 'http://sandbox-beta.open-metadata.org/api/v1/services/dashboardServices/2b9a4c6a-6dd1-43b1-b73e-434392f7e443', + }, + serviceType: 'Superset', + usageSummary: { + dailyStats: { + count: 0, + percentileRank: 0, + }, + weeklyStats: { + count: 0, + percentileRank: 0, + }, + monthlyStats: { + count: 0, + percentileRank: 0, + }, + date: '2022-12-12', + }, + deleted: false, + }, + { + id: '359e20da-d8d5-4f68-9694-417b8b24c74d', + name: '12', + displayName: 'Misc Charts', + fullyQualifiedName: 'sample_superset.12', + description: '', + version: 0.1, + updatedAt: 1668510719649, + updatedBy: 'ingestion-bot', + dashboardUrl: 'http://localhost:808/superset/dashboard/misc_charts/', + href: 'http://sandbox-beta.open-metadata.org/api/v1/dashboards/359e20da-d8d5-4f68-9694-417b8b24c74d', + tags: [], + service: { + id: '2b9a4c6a-6dd1-43b1-b73e-434392f7e443', + type: 'dashboardService', + name: 'sample_superset', + fullyQualifiedName: 'sample_superset', + deleted: false, + href: 'http://sandbox-beta.open-metadata.org/api/v1/services/dashboardServices/2b9a4c6a-6dd1-43b1-b73e-434392f7e443', + }, + serviceType: 'Superset', + usageSummary: { + dailyStats: { + count: 0, + percentileRank: 0, + }, + weeklyStats: { + count: 0, + percentileRank: 0, + }, + monthlyStats: { + count: 0, + percentileRank: 0, + }, + date: '2022-12-12', + }, + deleted: false, + }, +];