mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-08-27 18:36:08 +00:00
fix#10662: Update tier functionality is not working as expected on the containers page (#10720)
* fix#10662: Update tier functionality is not working as expected on the containers page * chore: remove unwanted change * test: add unit test * chore: add container entity icon * chore: fix spacing issue * chore: add support for lineage info drawer * fix: locale missing key issue * refactor: entity info drawer
This commit is contained in:
parent
fb02cbfeed
commit
c6746507d9
@ -14,17 +14,21 @@
|
|||||||
import { CloseOutlined } from '@ant-design/icons';
|
import { CloseOutlined } from '@ant-design/icons';
|
||||||
import { Col, Drawer, Row, Typography } from 'antd';
|
import { Col, Drawer, Row, Typography } from 'antd';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
import ContainerSummary from 'components/Explore/EntitySummaryPanel/ContainerSummary/ContainerSummary.component';
|
||||||
import DashboardSummary from 'components/Explore/EntitySummaryPanel/DashboardSummary/DashboardSummary.component';
|
import DashboardSummary from 'components/Explore/EntitySummaryPanel/DashboardSummary/DashboardSummary.component';
|
||||||
import MlModelSummary from 'components/Explore/EntitySummaryPanel/MlModelSummary/MlModelSummary.component';
|
import MlModelSummary from 'components/Explore/EntitySummaryPanel/MlModelSummary/MlModelSummary.component';
|
||||||
import PipelineSummary from 'components/Explore/EntitySummaryPanel/PipelineSummary/PipelineSummary.component';
|
import PipelineSummary from 'components/Explore/EntitySummaryPanel/PipelineSummary/PipelineSummary.component';
|
||||||
import TableSummary from 'components/Explore/EntitySummaryPanel/TableSummary/TableSummary.component';
|
import TableSummary from 'components/Explore/EntitySummaryPanel/TableSummary/TableSummary.component';
|
||||||
import TopicSummary from 'components/Explore/EntitySummaryPanel/TopicSummary/TopicSummary.component';
|
import TopicSummary from 'components/Explore/EntitySummaryPanel/TopicSummary/TopicSummary.component';
|
||||||
import { FQN_SEPARATOR_CHAR } from 'constants/char.constants';
|
import { FQN_SEPARATOR_CHAR } from 'constants/char.constants';
|
||||||
|
import { Container } from 'generated/entity/data/container';
|
||||||
import { Mlmodel } from 'generated/entity/data/mlmodel';
|
import { Mlmodel } from 'generated/entity/data/mlmodel';
|
||||||
|
import { EntityDetailUnion } from 'Models';
|
||||||
import React, { useEffect, useMemo, useState } from 'react';
|
import React, { useEffect, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { getDashboardByFqn } from 'rest/dashboardAPI';
|
import { getDashboardByFqn } from 'rest/dashboardAPI';
|
||||||
import { getMlModelByFQN } from 'rest/mlModelAPI';
|
import { getMlModelByFQN } from 'rest/mlModelAPI';
|
||||||
|
import { getContainerByName } from 'rest/objectStoreAPI';
|
||||||
import { getPipelineByFqn } from 'rest/pipelineAPI';
|
import { getPipelineByFqn } from 'rest/pipelineAPI';
|
||||||
import { getTableDetailsByFQN } from 'rest/tableAPI';
|
import { getTableDetailsByFQN } from 'rest/tableAPI';
|
||||||
import { getTopicByFqn } from 'rest/topicsAPI';
|
import { getTopicByFqn } from 'rest/topicsAPI';
|
||||||
@ -45,8 +49,6 @@ import { SelectedNode } from '../EntityLineage/EntityLineage.interface';
|
|||||||
import { LineageDrawerProps } from './EntityInfoDrawer.interface';
|
import { LineageDrawerProps } from './EntityInfoDrawer.interface';
|
||||||
import './EntityInfoDrawer.style.less';
|
import './EntityInfoDrawer.style.less';
|
||||||
|
|
||||||
type EntityData = Table | Pipeline | Dashboard | Topic | Mlmodel;
|
|
||||||
|
|
||||||
const EntityInfoDrawer = ({
|
const EntityInfoDrawer = ({
|
||||||
show,
|
show,
|
||||||
onCancel,
|
onCancel,
|
||||||
@ -54,137 +56,81 @@ const EntityInfoDrawer = ({
|
|||||||
isMainNode = false,
|
isMainNode = false,
|
||||||
}: LineageDrawerProps) => {
|
}: LineageDrawerProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [entityDetail, setEntityDetail] = useState<EntityData>(
|
const [entityDetail, setEntityDetail] = useState<EntityDetailUnion>(
|
||||||
{} as EntityData
|
{} as EntityDetailUnion
|
||||||
);
|
);
|
||||||
|
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
|
|
||||||
const fetchEntityDetail = (selectedNode: SelectedNode) => {
|
const fetchEntityDetail = async (selectedNode: SelectedNode) => {
|
||||||
|
let response = {};
|
||||||
|
const encodedFqn = getEncodedFqn(selectedNode.fqn);
|
||||||
|
const commonFields = ['tags', 'owner'];
|
||||||
|
|
||||||
|
setIsLoading(true);
|
||||||
|
try {
|
||||||
switch (selectedNode.type) {
|
switch (selectedNode.type) {
|
||||||
case EntityType.TABLE: {
|
case EntityType.TABLE: {
|
||||||
setIsLoading(true);
|
response = await getTableDetailsByFQN(encodedFqn, [
|
||||||
getTableDetailsByFQN(getEncodedFqn(selectedNode.fqn), [
|
...commonFields,
|
||||||
'tags',
|
|
||||||
'owner',
|
|
||||||
'columns',
|
'columns',
|
||||||
'usageSummary',
|
'usageSummary',
|
||||||
'profile',
|
'profile',
|
||||||
])
|
]);
|
||||||
.then((res) => {
|
|
||||||
setEntityDetail(res);
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
showErrorToast(
|
|
||||||
t('server.error-selected-node-name-details', {
|
|
||||||
selectedNodeName: selectedNode.name,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setIsLoading(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case EntityType.PIPELINE: {
|
case EntityType.PIPELINE: {
|
||||||
setIsLoading(true);
|
response = await getPipelineByFqn(encodedFqn, [
|
||||||
getPipelineByFqn(getEncodedFqn(selectedNode.fqn), [
|
...commonFields,
|
||||||
'tags',
|
|
||||||
'owner',
|
|
||||||
'followers',
|
'followers',
|
||||||
'tasks',
|
'tasks',
|
||||||
'tier',
|
]);
|
||||||
])
|
|
||||||
.then((res) => {
|
|
||||||
setEntityDetail(res);
|
|
||||||
setIsLoading(false);
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
showErrorToast(
|
|
||||||
t('server.error-selected-node-name-details', {
|
|
||||||
selectedNodeName: selectedNode.name,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setIsLoading(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case EntityType.TOPIC: {
|
case EntityType.TOPIC: {
|
||||||
setIsLoading(true);
|
response = await getTopicByFqn(encodedFqn ?? '', commonFields);
|
||||||
getTopicByFqn(selectedNode.fqn ?? '', ['tags', 'owner'])
|
|
||||||
.then((res) => {
|
|
||||||
setEntityDetail(res);
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
showErrorToast(
|
|
||||||
t('server.error-selected-node-name-details', {
|
|
||||||
selectedNodeName: selectedNode.name,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setIsLoading(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case EntityType.DASHBOARD: {
|
case EntityType.DASHBOARD: {
|
||||||
setIsLoading(true);
|
response = await getDashboardByFqn(encodedFqn, [
|
||||||
getDashboardByFqn(getEncodedFqn(selectedNode.fqn), [
|
...commonFields,
|
||||||
'tags',
|
|
||||||
'owner',
|
|
||||||
'charts',
|
'charts',
|
||||||
])
|
]);
|
||||||
.then((res) => {
|
|
||||||
setEntityDetail(res);
|
|
||||||
setIsLoading(false);
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
showErrorToast(
|
|
||||||
t('server.error-selected-node-name-details', {
|
|
||||||
selectedNodeName: selectedNode.name,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setIsLoading(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
case EntityType.MLMODEL: {
|
case EntityType.MLMODEL: {
|
||||||
setIsLoading(true);
|
response = await getMlModelByFQN(encodedFqn, [
|
||||||
getMlModelByFQN(getEncodedFqn(selectedNode.fqn), [
|
...commonFields,
|
||||||
'tags',
|
|
||||||
'owner',
|
|
||||||
'dashboard',
|
'dashboard',
|
||||||
])
|
]);
|
||||||
.then((res) => {
|
|
||||||
setEntityDetail(res);
|
break;
|
||||||
setIsLoading(false);
|
}
|
||||||
})
|
case EntityType.CONTAINER: {
|
||||||
.catch(() => {
|
response = await getContainerByName(
|
||||||
showErrorToast(
|
encodedFqn,
|
||||||
t('server.error-selected-node-name-details', {
|
'dataModel,owner,tags'
|
||||||
selectedNodeName: selectedNode.name,
|
|
||||||
})
|
|
||||||
);
|
);
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
setIsLoading(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
setEntityDetail(response);
|
||||||
|
} catch (error) {
|
||||||
|
showErrorToast(
|
||||||
|
t('server.error-selected-node-name-details', {
|
||||||
|
selectedNodeName: selectedNode.name,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const tags = useMemo(
|
const tags = useMemo(
|
||||||
@ -243,6 +189,15 @@ const EntityInfoDrawer = ({
|
|||||||
tags={tags}
|
tags={tags}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
case EntityType.CONTAINER:
|
||||||
|
return (
|
||||||
|
<ContainerSummary
|
||||||
|
componentType={DRAWER_NAVIGATION_OPTIONS.lineage}
|
||||||
|
entityDetails={entityDetail as Container}
|
||||||
|
isLoading={isLoading}
|
||||||
|
tags={tags}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
|
@ -160,7 +160,10 @@ const SuccessScreen = ({
|
|||||||
theme="primary"
|
theme="primary"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
onClick={handleViewServiceClick}>
|
onClick={handleViewServiceClick}>
|
||||||
<span>{viewServiceText ?? 'View Service'}</span>
|
<span>
|
||||||
|
{viewServiceText ??
|
||||||
|
t('label.view-entity', { entity: t('label.service') })}
|
||||||
|
</span>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{showIngestionButton && (
|
{showIngestionButton && (
|
||||||
|
@ -247,4 +247,12 @@ declare module 'Models' {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type PagingWithoutTotal = Omit<Paging, 'total'>;
|
export type PagingWithoutTotal = Omit<Paging, 'total'>;
|
||||||
|
|
||||||
|
type EntityDetailUnion =
|
||||||
|
| Table
|
||||||
|
| Pipeline
|
||||||
|
| Dashboard
|
||||||
|
| Topic
|
||||||
|
| Mlmodel
|
||||||
|
| Container;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
* 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 CONTAINER_DATA = {
|
||||||
|
id: '5d11e32a-8673-4a84-a9be-ccd9651ba9fc',
|
||||||
|
name: 'transactions',
|
||||||
|
fullyQualifiedName: 's3_object_store_sample.transactions',
|
||||||
|
displayName: 'Company Transactions',
|
||||||
|
description: "Bucket containing all the company's transactions",
|
||||||
|
version: 0.7,
|
||||||
|
updatedAt: 1679567030351,
|
||||||
|
updatedBy: 'admin',
|
||||||
|
href: 'http://localhost:8585/api/v1/containers/5d11e32a-8673-4a84-a9be-ccd9651ba9fc',
|
||||||
|
owner: {
|
||||||
|
id: '28b43857-288b-4e4e-8fac-c9cd34e06393',
|
||||||
|
type: 'team',
|
||||||
|
name: 'Applications',
|
||||||
|
fullyQualifiedName: 'Applications',
|
||||||
|
deleted: false,
|
||||||
|
href: 'http://localhost:8585/api/v1/teams/28b43857-288b-4e4e-8fac-c9cd34e06393',
|
||||||
|
},
|
||||||
|
service: {
|
||||||
|
id: 'cbc2a5e8-b7d3-4140-9a44-a4b331e5372f',
|
||||||
|
type: 'objectStoreService',
|
||||||
|
name: 's3_object_store_sample',
|
||||||
|
fullyQualifiedName: 's3_object_store_sample',
|
||||||
|
deleted: false,
|
||||||
|
href: 'http://localhost:8585/api/v1/services/objectstoreServices/cbc2a5e8-b7d3-4140-9a44-a4b331e5372f',
|
||||||
|
},
|
||||||
|
dataModel: {
|
||||||
|
isPartitioned: true,
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'transaction_id',
|
||||||
|
dataType: 'NUMERIC',
|
||||||
|
dataTypeDisplay: 'numeric',
|
||||||
|
description:
|
||||||
|
'The ID of the executed transaction. This column is the primary key for this table.',
|
||||||
|
fullyQualifiedName:
|
||||||
|
's3_object_store_sample.transactions.transaction_id',
|
||||||
|
tags: [],
|
||||||
|
constraint: 'PRIMARY_KEY',
|
||||||
|
ordinalPosition: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'merchant',
|
||||||
|
dataType: 'VARCHAR',
|
||||||
|
dataLength: 100,
|
||||||
|
dataTypeDisplay: 'varchar',
|
||||||
|
description: 'The merchant for this transaction.',
|
||||||
|
fullyQualifiedName: 's3_object_store_sample.transactions.merchant',
|
||||||
|
tags: [],
|
||||||
|
ordinalPosition: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'transaction_time',
|
||||||
|
dataType: 'TIMESTAMP',
|
||||||
|
dataTypeDisplay: 'timestamp',
|
||||||
|
description: 'The time the transaction took place.',
|
||||||
|
fullyQualifiedName:
|
||||||
|
's3_object_store_sample.transactions.transaction_time',
|
||||||
|
tags: [],
|
||||||
|
ordinalPosition: 3,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
prefix: '/transactions/',
|
||||||
|
numberOfObjects: 50,
|
||||||
|
size: 102400,
|
||||||
|
fileFormats: ['parquet'],
|
||||||
|
serviceType: 'S3',
|
||||||
|
followers: [],
|
||||||
|
tags: [
|
||||||
|
{
|
||||||
|
tagFQN: 'Tier.Tier5',
|
||||||
|
description: '',
|
||||||
|
source: 'Classification',
|
||||||
|
labelType: 'Manual',
|
||||||
|
state: 'Confirmed',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
deleted: false,
|
||||||
|
};
|
@ -0,0 +1,246 @@
|
|||||||
|
/*
|
||||||
|
* 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 { getContainerByName } from 'rest/objectStoreAPI';
|
||||||
|
import ContainerPage from './ContainerPage';
|
||||||
|
import { CONTAINER_DATA } from './ContainerPage.mock';
|
||||||
|
|
||||||
|
jest.mock('components/PermissionProvider/PermissionProvider', () => ({
|
||||||
|
usePermissionProvider: jest.fn().mockImplementation(() => ({
|
||||||
|
getEntityPermissionByFqn: jest.fn().mockResolvedValue({
|
||||||
|
Create: true,
|
||||||
|
Delete: true,
|
||||||
|
EditAll: true,
|
||||||
|
EditCustomFields: true,
|
||||||
|
EditDataProfile: true,
|
||||||
|
EditDescription: true,
|
||||||
|
EditDisplayName: true,
|
||||||
|
EditLineage: true,
|
||||||
|
EditOwner: true,
|
||||||
|
EditQueries: true,
|
||||||
|
EditSampleData: true,
|
||||||
|
EditTags: true,
|
||||||
|
EditTests: true,
|
||||||
|
EditTier: true,
|
||||||
|
ViewAll: true,
|
||||||
|
ViewDataProfile: true,
|
||||||
|
ViewQueries: true,
|
||||||
|
ViewSampleData: true,
|
||||||
|
ViewTests: true,
|
||||||
|
ViewUsage: true,
|
||||||
|
}),
|
||||||
|
})),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('components/common/CustomPropertyTable/CustomPropertyTable', () => {
|
||||||
|
return {
|
||||||
|
CustomPropertyTable: jest
|
||||||
|
.fn()
|
||||||
|
.mockReturnValue(
|
||||||
|
<div data-testid="custom-properties-table">CustomPropertyTable</div>
|
||||||
|
),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.mock('components/common/description/Description', () => {
|
||||||
|
return jest
|
||||||
|
.fn()
|
||||||
|
.mockReturnValue(<div data-testid="description">Description</div>);
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.mock('components/common/entityPageInfo/EntityPageInfo', () => {
|
||||||
|
return jest
|
||||||
|
.fn()
|
||||||
|
.mockReturnValue(<div data-testid="entity-page-info">EntityPageInfo</div>);
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.mock('components/common/error-with-placeholder/ErrorPlaceHolder', () => {
|
||||||
|
return jest
|
||||||
|
.fn()
|
||||||
|
.mockReturnValue(
|
||||||
|
<div data-testid="error-placeholder">ErrorPlaceHolder</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.mock(
|
||||||
|
'components/ContainerDetail/ContainerChildren/ContainerChildren',
|
||||||
|
() => {
|
||||||
|
return jest
|
||||||
|
.fn()
|
||||||
|
.mockReturnValue(
|
||||||
|
<div data-testid="containers-children">ContainerChildren</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
jest.mock(
|
||||||
|
'components/ContainerDetail/ContainerDataModel/ContainerDataModel',
|
||||||
|
() => {
|
||||||
|
return jest
|
||||||
|
.fn()
|
||||||
|
.mockReturnValue(
|
||||||
|
<div data-testid="container-data-model">ContainerDataModel</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
jest.mock('components/containers/PageContainerV1', () => {
|
||||||
|
return jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementation(({ children }) => (
|
||||||
|
<div data-testid="container-children">{children}</div>
|
||||||
|
));
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.mock('components/EntityLineage/EntityLineage.component', () => {
|
||||||
|
return jest
|
||||||
|
.fn()
|
||||||
|
.mockReturnValue(<div data-testid="entity-lineage">EntityLineage</div>);
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.mock('components/Loader/Loader', () => {
|
||||||
|
return jest.fn().mockReturnValue(<div data-testid="loader">Loader</div>);
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.mock('rest/lineageAPI', () => ({
|
||||||
|
getLineageByFQN: jest.fn().mockImplementation(() => Promise.resolve()),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('rest/miscAPI', () => ({
|
||||||
|
deleteLineageEdge: jest.fn().mockImplementation(() => Promise.resolve()),
|
||||||
|
addLineage: jest.fn().mockImplementation(() => Promise.resolve()),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('rest/objectStoreAPI', () => ({
|
||||||
|
addContainerFollower: jest.fn().mockImplementation(() => Promise.resolve()),
|
||||||
|
getContainerByName: jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementation(() => Promise.resolve(CONTAINER_DATA)),
|
||||||
|
patchContainerDetails: jest.fn().mockImplementation(() => Promise.resolve()),
|
||||||
|
removeContainerFollower: jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementation(() => Promise.resolve()),
|
||||||
|
restoreContainer: jest.fn().mockImplementation(() => Promise.resolve()),
|
||||||
|
}));
|
||||||
|
|
||||||
|
let mockParams = {
|
||||||
|
entityFQN: 'entityTypeFQN',
|
||||||
|
tab: 'schema',
|
||||||
|
};
|
||||||
|
|
||||||
|
jest.mock('react-router-dom', () => ({
|
||||||
|
useHistory: jest.fn(),
|
||||||
|
useParams: jest.fn().mockImplementation(() => mockParams),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('Container Page Component', () => {
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should render the child components', async () => {
|
||||||
|
await act(async () => {
|
||||||
|
render(<ContainerPage />, {
|
||||||
|
wrapper: MemoryRouter,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const pageTopInfo = screen.getByTestId('entity-page-info');
|
||||||
|
const tabs = screen.getAllByRole('tab');
|
||||||
|
|
||||||
|
expect(pageTopInfo).toBeInTheDocument();
|
||||||
|
expect(tabs).toHaveLength(4);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should render the schema tab component', async () => {
|
||||||
|
await act(async () => {
|
||||||
|
render(<ContainerPage />, {
|
||||||
|
wrapper: MemoryRouter,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const tabs = screen.getAllByRole('tab');
|
||||||
|
|
||||||
|
const schemaTab = tabs[0];
|
||||||
|
|
||||||
|
expect(schemaTab).toHaveAttribute('aria-selected', 'true');
|
||||||
|
|
||||||
|
const description = screen.getByTestId('description');
|
||||||
|
|
||||||
|
expect(description).toBeInTheDocument();
|
||||||
|
|
||||||
|
const dataModel = screen.getByTestId('container-data-model');
|
||||||
|
|
||||||
|
expect(dataModel).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should render the children tab component', async () => {
|
||||||
|
mockParams = { ...mockParams, tab: 'children' };
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
render(<ContainerPage />, {
|
||||||
|
wrapper: MemoryRouter,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const containerChildren = screen.getByTestId('containers-children');
|
||||||
|
|
||||||
|
expect(containerChildren).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should render the lineage tab component', async () => {
|
||||||
|
mockParams = { ...mockParams, tab: 'lineage' };
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
render(<ContainerPage />, {
|
||||||
|
wrapper: MemoryRouter,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const lineage = screen.getByTestId('entity-lineage');
|
||||||
|
|
||||||
|
expect(lineage).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should render the custom properties tab component', async () => {
|
||||||
|
mockParams = { ...mockParams, tab: 'custom-properties' };
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
render(<ContainerPage />, {
|
||||||
|
wrapper: MemoryRouter,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const customPropertyTable = screen.getByTestId('custom-properties-table');
|
||||||
|
|
||||||
|
expect(customPropertyTable).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should render error placeholder on API fail', async () => {
|
||||||
|
mockParams = { ...mockParams, tab: 'schema' };
|
||||||
|
(getContainerByName as jest.Mock).mockImplementationOnce(() =>
|
||||||
|
Promise.reject()
|
||||||
|
);
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
render(<ContainerPage />, {
|
||||||
|
wrapper: MemoryRouter,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const errorPlaceholder = screen.getByTestId('error-placeholder');
|
||||||
|
|
||||||
|
expect(errorPlaceholder).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
@ -37,7 +37,6 @@ import {
|
|||||||
} from 'components/PermissionProvider/PermissionProvider.interface';
|
} from 'components/PermissionProvider/PermissionProvider.interface';
|
||||||
import { FQN_SEPARATOR_CHAR } from 'constants/char.constants';
|
import { FQN_SEPARATOR_CHAR } from 'constants/char.constants';
|
||||||
import { getServiceDetailsPath } from 'constants/constants';
|
import { getServiceDetailsPath } from 'constants/constants';
|
||||||
import { ENTITY_CARD_CLASS } from 'constants/entity.constants';
|
|
||||||
import { NO_PERMISSION_TO_VIEW } from 'constants/HelperTextUtil';
|
import { NO_PERMISSION_TO_VIEW } from 'constants/HelperTextUtil';
|
||||||
import { EntityInfo, EntityType } from 'enums/entity.enum';
|
import { EntityInfo, EntityType } from 'enums/entity.enum';
|
||||||
import { ServiceCategory } from 'enums/service.enum';
|
import { ServiceCategory } from 'enums/service.enum';
|
||||||
@ -426,7 +425,7 @@ const ContainerPage = () => {
|
|||||||
const { tags: newTags, version } = await handleUpdateContainerData({
|
const { tags: newTags, version } = await handleUpdateContainerData({
|
||||||
...(containerData as Container),
|
...(containerData as Container),
|
||||||
tags: [
|
tags: [
|
||||||
...(containerData?.tags ?? []),
|
...getTagsWithoutTier(containerData?.tags ?? []),
|
||||||
{
|
{
|
||||||
tagFQN: updatedTier,
|
tagFQN: updatedTier,
|
||||||
labelType: LabelType.Manual,
|
labelType: LabelType.Manual,
|
||||||
@ -705,7 +704,7 @@ const ContainerPage = () => {
|
|||||||
</span>
|
</span>
|
||||||
}>
|
}>
|
||||||
<Card
|
<Card
|
||||||
className={`${ENTITY_CARD_CLASS} card-body-full`}
|
className="h-full card-body-full"
|
||||||
data-testid="lineage-details">
|
data-testid="lineage-details">
|
||||||
<EntityLineageComponent
|
<EntityLineageComponent
|
||||||
addLineageHandler={handleAddLineage}
|
addLineageHandler={handleAddLineage}
|
||||||
@ -732,7 +731,7 @@ const ContainerPage = () => {
|
|||||||
{t('label.custom-property-plural')}
|
{t('label.custom-property-plural')}
|
||||||
</span>
|
</span>
|
||||||
}>
|
}>
|
||||||
<Card className={ENTITY_CARD_CLASS}>
|
<Card className="h-full">
|
||||||
<CustomPropertyTable
|
<CustomPropertyTable
|
||||||
entityDetails={
|
entityDetails={
|
||||||
containerData as CustomPropertyProps['entityDetails']
|
containerData as CustomPropertyProps['entityDetails']
|
||||||
|
@ -24,7 +24,7 @@ import { Container } from 'generated/entity/data/container';
|
|||||||
import { Mlmodel } from 'generated/entity/data/mlmodel';
|
import { Mlmodel } from 'generated/entity/data/mlmodel';
|
||||||
import i18next from 'i18next';
|
import i18next from 'i18next';
|
||||||
import { isEmpty, isNil, isUndefined, lowerCase, startCase } from 'lodash';
|
import { isEmpty, isNil, isUndefined, lowerCase, startCase } from 'lodash';
|
||||||
import { Bucket } from 'Models';
|
import { Bucket, EntityDetailUnion } from 'Models';
|
||||||
import React, { Fragment } from 'react';
|
import React, { Fragment } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { FQN_SEPARATOR_CHAR } from '../constants/char.constants';
|
import { FQN_SEPARATOR_CHAR } from '../constants/char.constants';
|
||||||
@ -48,7 +48,6 @@ import {
|
|||||||
Table,
|
Table,
|
||||||
TableType,
|
TableType,
|
||||||
} from '../generated/entity/data/table';
|
} from '../generated/entity/data/table';
|
||||||
import { Topic } from '../generated/entity/data/topic';
|
|
||||||
import { Edge, EntityLineage } from '../generated/type/entityLineage';
|
import { Edge, EntityLineage } from '../generated/type/entityLineage';
|
||||||
import { EntityReference } from '../generated/type/entityUsage';
|
import { EntityReference } from '../generated/type/entityUsage';
|
||||||
import { TagLabel } from '../generated/type/tagLabel';
|
import { TagLabel } from '../generated/type/tagLabel';
|
||||||
@ -85,7 +84,7 @@ export const getEntityId = (entity?: { id?: string }) => entity?.id || '';
|
|||||||
|
|
||||||
export const getEntityTags = (
|
export const getEntityTags = (
|
||||||
type: string,
|
type: string,
|
||||||
entityDetail: Table | Pipeline | Dashboard | Topic | Mlmodel
|
entityDetail: EntityDetailUnion
|
||||||
): Array<TagLabel> => {
|
): Array<TagLabel> => {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case EntityType.TABLE: {
|
case EntityType.TABLE: {
|
||||||
@ -434,25 +433,30 @@ export const getEntityOverview = (
|
|||||||
const { numberOfObjects, serviceType, dataModel } =
|
const { numberOfObjects, serviceType, dataModel } =
|
||||||
entityDetail as Container;
|
entityDetail as Container;
|
||||||
|
|
||||||
|
const visible = [
|
||||||
|
DRAWER_NAVIGATION_OPTIONS.lineage,
|
||||||
|
DRAWER_NAVIGATION_OPTIONS.explore,
|
||||||
|
];
|
||||||
|
|
||||||
const overview = [
|
const overview = [
|
||||||
{
|
{
|
||||||
name: i18next.t('label.number-of-object'),
|
name: i18next.t('label.number-of-object'),
|
||||||
value: numberOfObjects,
|
value: numberOfObjects,
|
||||||
isLink: false,
|
isLink: false,
|
||||||
visible: [DRAWER_NAVIGATION_OPTIONS.explore],
|
visible,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: i18next.t('label.service-type'),
|
name: i18next.t('label.service-type'),
|
||||||
value: serviceType,
|
value: serviceType,
|
||||||
isLink: false,
|
isLink: false,
|
||||||
visible: [DRAWER_NAVIGATION_OPTIONS.explore],
|
visible,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: i18next.t('label.column-plural'),
|
name: i18next.t('label.column-plural'),
|
||||||
value:
|
value:
|
||||||
dataModel && dataModel.columns ? dataModel.columns.length : NO_DATA,
|
dataModel && dataModel.columns ? dataModel.columns.length : NO_DATA,
|
||||||
isLink: false,
|
isLink: false,
|
||||||
visible: [DRAWER_NAVIGATION_OPTIONS.explore],
|
visible,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
import Icon from '@ant-design/icons';
|
import Icon from '@ant-design/icons';
|
||||||
import { Tooltip } from 'antd';
|
import { Tooltip } from 'antd';
|
||||||
import { ExpandableConfig } from 'antd/lib/table/interface';
|
import { ExpandableConfig } from 'antd/lib/table/interface';
|
||||||
|
import { ReactComponent as ContainerIcon } from 'assets/svg/ic-object-store.svg';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import { upperCase } from 'lodash';
|
import { upperCase } from 'lodash';
|
||||||
@ -321,6 +322,10 @@ export const getEntityIcon = (indexType: string) => {
|
|||||||
case EntityType.PIPELINE:
|
case EntityType.PIPELINE:
|
||||||
return <PipelineIcon />;
|
return <PipelineIcon />;
|
||||||
|
|
||||||
|
case SearchIndex.CONTAINER:
|
||||||
|
case EntityType.CONTAINER:
|
||||||
|
return <ContainerIcon />;
|
||||||
|
|
||||||
case SearchIndex.TABLE:
|
case SearchIndex.TABLE:
|
||||||
case EntityType.TABLE:
|
case EntityType.TABLE:
|
||||||
default:
|
default:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user