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
This commit is contained in:
Sachin Chaurasiya 2022-12-12 20:03:21 +05:30 committed by GitHub
parent 9a955036cf
commit afa93cbba2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 388 additions and 35 deletions

View File

@ -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', () => ({
<span>{children}</span>
)),
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(<div>ManageButton</div>);
}
);
jest.mock('../../components/Ingestion/Ingestion.component', () => {
return jest
.fn()
.mockReturnValue(<div data-testid="ingestions">Ingestion</div>);
});
jest.mock(
'../../components/ServiceConnectionDetails/ServiceConnectionDetails.component',
() => {
return jest
.fn()
.mockReturnValue(
<div data-testid="service-connections">ServiceConnectionDetails</div>
);
}
);
jest.mock('../../components/tags-viewer/tags-viewer', () => {
return jest
.fn()
.mockReturnValue(<div data-testid="tag-viewer">Tag Viewer</div>);
});
jest.mock('../../components/common/ProfilePicture/ProfilePicture', () => {
return jest.fn().mockImplementation(({ name }) => {
return <div data-testid={`${name}-profile`}>{name}</div>;
});
});
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(<ServicePage />, {
it('Should render the service children table rows', async () => {
render(<ServicePage />, {
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(<ServicePage />, {
it('Should render the owner name and profile pic if child has owner', async () => {
render(<ServicePage />, {
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(<ServicePage />, {
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(<ServicePage />, {
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(<ServicePage />, {
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();
});
});

View File

@ -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() ? (
<RichTextEditorPreviewer markdown={text} />
render: (description: ServicePageData['description']) =>
!isUndefined(description) && description.trim() ? (
<RichTextEditorPreviewer markdown={description} />
) : (
<span className="tw-no-description">
{t('label.no-description')}
@ -940,16 +941,30 @@ const ServicePage: FunctionComponent = () => {
title: t('label.owner'),
dataIndex: 'owner',
key: 'owner',
render: (record: ServicePageData) => (
<p>{record?.owner?.name || '--'}</p>
),
render: (owner: ServicePageData['owner']) =>
!isUndefined(owner) ? (
<Space data-testid="owner-data">
<ProfilePicture
id=""
name={owner.name ?? ''}
type="circle"
width="24"
/>
<Typography.Text data-testid={`${owner.name}-owner-name`}>
{getEntityName(owner)}
</Typography.Text>
</Space>
) : (
<Typography.Text data-testid="no-owner-text">--</Typography.Text>
),
},
{
title: lastColumn,
dataIndex: toLower(lastColumn),
key: toLower(lastColumn),
render: (record: ServicePageData) =>
getOptionalTableCells(record as Database),
render: (_, record: ServicePageData) => (
<div data-testid="record-tags">{getOptionalTableCells(record)}</div>
),
},
];
}, []);
@ -1080,10 +1095,10 @@ const ServicePage: FunctionComponent = () => {
children,
}: {
children: React.ReactNode;
}) => <tr data-testid="column">{children}</tr>,
}) => <tr data-testid="row">{children}</tr>,
},
}}
data-testid="database-table"
data-testid="service-children-table"
dataSource={data}
pagination={false}
rowKey="id"

View File

@ -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,
},
];