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