mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-10-16 19:28:21 +00:00
feat(ui): supported latest ui for the database page (#12277)
* supported latest ui for the database page * fix unit test
This commit is contained in:
parent
9ae3407be0
commit
f8ff645e5d
@ -37,6 +37,7 @@ import { EntityTabs, EntityType } from 'enums/entity.enum';
|
|||||||
import { Container } from 'generated/entity/data/container';
|
import { Container } from 'generated/entity/data/container';
|
||||||
import { Dashboard } from 'generated/entity/data/dashboard';
|
import { Dashboard } from 'generated/entity/data/dashboard';
|
||||||
import { DashboardDataModel } from 'generated/entity/data/dashboardDataModel';
|
import { DashboardDataModel } from 'generated/entity/data/dashboardDataModel';
|
||||||
|
import { Database } from 'generated/entity/data/database';
|
||||||
import { Mlmodel } from 'generated/entity/data/mlmodel';
|
import { Mlmodel } from 'generated/entity/data/mlmodel';
|
||||||
import { Pipeline } from 'generated/entity/data/pipeline';
|
import { Pipeline } from 'generated/entity/data/pipeline';
|
||||||
import { Table } from 'generated/entity/data/table';
|
import { Table } from 'generated/entity/data/table';
|
||||||
@ -56,6 +57,7 @@ import { getCurrentUserId, getEntityDetailLink } from 'utils/CommonUtils';
|
|||||||
import {
|
import {
|
||||||
getBreadcrumbForEntitiesWithServiceOnly,
|
getBreadcrumbForEntitiesWithServiceOnly,
|
||||||
getBreadcrumbForTable,
|
getBreadcrumbForTable,
|
||||||
|
getEntityBreadcrumbs,
|
||||||
getEntityFeedLink,
|
getEntityFeedLink,
|
||||||
getEntityName,
|
getEntityName,
|
||||||
} from 'utils/EntityUtils';
|
} from 'utils/EntityUtils';
|
||||||
@ -66,6 +68,7 @@ import { showErrorToast } from 'utils/ToastUtils';
|
|||||||
import {
|
import {
|
||||||
DataAssetHeaderInfo,
|
DataAssetHeaderInfo,
|
||||||
DataAssetsHeaderProps,
|
DataAssetsHeaderProps,
|
||||||
|
DataAssetType,
|
||||||
} from './DataAssetsHeader.interface';
|
} from './DataAssetsHeader.interface';
|
||||||
|
|
||||||
export const ExtraInfoLabel = ({
|
export const ExtraInfoLabel = ({
|
||||||
@ -108,6 +111,7 @@ export const ExtraInfoLink = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
export const DataAssetsHeader = ({
|
export const DataAssetsHeader = ({
|
||||||
|
allowSoftDelete = true,
|
||||||
dataAsset,
|
dataAsset,
|
||||||
onOwnerUpdate,
|
onOwnerUpdate,
|
||||||
onTierUpdate,
|
onTierUpdate,
|
||||||
@ -115,6 +119,7 @@ export const DataAssetsHeader = ({
|
|||||||
onVersionClick,
|
onVersionClick,
|
||||||
onFollowClick,
|
onFollowClick,
|
||||||
entityType,
|
entityType,
|
||||||
|
isRecursiveDelete,
|
||||||
onRestoreDataAsset,
|
onRestoreDataAsset,
|
||||||
onDisplayNameUpdate,
|
onDisplayNameUpdate,
|
||||||
}: DataAssetsHeaderProps) => {
|
}: DataAssetsHeaderProps) => {
|
||||||
@ -133,13 +138,23 @@ export const DataAssetsHeader = ({
|
|||||||
);
|
);
|
||||||
const [copyTooltip, setCopyTooltip] = useState<string>();
|
const [copyTooltip, setCopyTooltip] = useState<string>();
|
||||||
|
|
||||||
|
const excludeEntityService = [EntityType.DATABASE].includes(entityType);
|
||||||
|
const hasFollowers = 'followers' in dataAsset;
|
||||||
|
|
||||||
const { entityName, tier, isFollowing, version, followers } = useMemo(
|
const { entityName, tier, isFollowing, version, followers } = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
isFollowing: dataAsset.followers?.some(({ id }) => id === USERId),
|
isFollowing: hasFollowers
|
||||||
|
? (dataAsset as DataAssetType).followers?.some(
|
||||||
|
({ id }) => id === USERId
|
||||||
|
)
|
||||||
|
: false,
|
||||||
|
followers: hasFollowers
|
||||||
|
? (dataAsset as DataAssetType).followers?.length
|
||||||
|
: 0,
|
||||||
|
|
||||||
tier: getTierTags(dataAsset.tags ?? []),
|
tier: getTierTags(dataAsset.tags ?? []),
|
||||||
entityName: getEntityName(dataAsset),
|
entityName: getEntityName(dataAsset),
|
||||||
version: dataAsset.version,
|
version: dataAsset.version,
|
||||||
followers: dataAsset.followers?.length,
|
|
||||||
}),
|
}),
|
||||||
[dataAsset, USERId]
|
[dataAsset, USERId]
|
||||||
);
|
);
|
||||||
@ -363,6 +378,16 @@ export const DataAssetsHeader = ({
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case EntityType.DATABASE:
|
||||||
|
const databaseDetails = dataAsset as Database;
|
||||||
|
|
||||||
|
returnData.breadcrumbs = getEntityBreadcrumbs(
|
||||||
|
databaseDetails,
|
||||||
|
EntityType.DATABASE
|
||||||
|
);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
case EntityType.TABLE:
|
case EntityType.TABLE:
|
||||||
default:
|
default:
|
||||||
const tableDetails = dataAsset as Table;
|
const tableDetails = dataAsset as Table;
|
||||||
@ -492,27 +517,36 @@ export const DataAssetsHeader = ({
|
|||||||
<Space className="items-end w-full" direction="vertical" size={16}>
|
<Space className="items-end w-full" direction="vertical" size={16}>
|
||||||
<Space>
|
<Space>
|
||||||
<ButtonGroup size="small">
|
<ButtonGroup size="small">
|
||||||
<Button
|
{!excludeEntityService && (
|
||||||
className="w-16 p-0"
|
<>
|
||||||
icon={<Icon component={TaskOpenIcon} />}
|
<Button
|
||||||
onClick={handleOpenTaskClick}>
|
className="w-16 p-0"
|
||||||
<Typography.Text>{taskCount}</Typography.Text>
|
icon={<Icon component={TaskOpenIcon} />}
|
||||||
</Button>
|
onClick={handleOpenTaskClick}>
|
||||||
<Button
|
<Typography.Text>{taskCount}</Typography.Text>
|
||||||
className="w-16 p-0"
|
</Button>
|
||||||
icon={<Icon component={VersionIcon} />}
|
|
||||||
onClick={onVersionClick}>
|
<Button
|
||||||
<Typography.Text>{version}</Typography.Text>
|
className="w-16 p-0"
|
||||||
</Button>
|
icon={<Icon component={VersionIcon} />}
|
||||||
<Button
|
onClick={onVersionClick}>
|
||||||
className="w-16 p-0"
|
<Typography.Text>{version}</Typography.Text>
|
||||||
data-testid="entity-follow-button"
|
</Button>
|
||||||
icon={
|
|
||||||
<Icon component={isFollowing ? StarFilledIcon : StarIcon} />
|
<Button
|
||||||
}
|
className="w-16 p-0"
|
||||||
onClick={onFollowClick}>
|
data-testid="entity-follow-button"
|
||||||
<Typography.Text>{followers}</Typography.Text>
|
icon={
|
||||||
</Button>
|
<Icon
|
||||||
|
component={isFollowing ? StarFilledIcon : StarIcon}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
onClick={onFollowClick}>
|
||||||
|
<Typography.Text>{followers}</Typography.Text>
|
||||||
|
</Button>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
<Tooltip
|
<Tooltip
|
||||||
open={!isEmpty(copyTooltip)}
|
open={!isEmpty(copyTooltip)}
|
||||||
placement="bottomRight"
|
placement="bottomRight"
|
||||||
@ -523,7 +557,7 @@ export const DataAssetsHeader = ({
|
|||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
<ManageButton
|
<ManageButton
|
||||||
allowSoftDelete={!dataAsset.deleted}
|
allowSoftDelete={!dataAsset.deleted && allowSoftDelete}
|
||||||
canDelete={permissions.Delete}
|
canDelete={permissions.Delete}
|
||||||
deleted={dataAsset.deleted}
|
deleted={dataAsset.deleted}
|
||||||
displayName={dataAsset.displayName}
|
displayName={dataAsset.displayName}
|
||||||
@ -534,6 +568,7 @@ export const DataAssetsHeader = ({
|
|||||||
entityId={dataAsset.id}
|
entityId={dataAsset.id}
|
||||||
entityName={entityName}
|
entityName={entityName}
|
||||||
entityType={entityType}
|
entityType={entityType}
|
||||||
|
isRecursiveDelete={isRecursiveDelete}
|
||||||
onAnnouncementClick={
|
onAnnouncementClick={
|
||||||
permissions?.EditAll
|
permissions?.EditAll
|
||||||
? () => setIsAnnouncementDrawer(true)
|
? () => setIsAnnouncementDrawer(true)
|
||||||
|
@ -17,6 +17,7 @@ import { EntityType } from 'enums/entity.enum';
|
|||||||
import { Container } from 'generated/entity/data/container';
|
import { Container } from 'generated/entity/data/container';
|
||||||
import { Dashboard } from 'generated/entity/data/dashboard';
|
import { Dashboard } from 'generated/entity/data/dashboard';
|
||||||
import { DashboardDataModel } from 'generated/entity/data/dashboardDataModel';
|
import { DashboardDataModel } from 'generated/entity/data/dashboardDataModel';
|
||||||
|
import { Database } from 'generated/entity/data/database';
|
||||||
import { Mlmodel } from 'generated/entity/data/mlmodel';
|
import { Mlmodel } from 'generated/entity/data/mlmodel';
|
||||||
import { Pipeline } from 'generated/entity/data/pipeline';
|
import { Pipeline } from 'generated/entity/data/pipeline';
|
||||||
import { Table } from 'generated/entity/data/table';
|
import { Table } from 'generated/entity/data/table';
|
||||||
@ -24,21 +25,23 @@ import { Topic } from 'generated/entity/data/topic';
|
|||||||
import { EntityReference } from 'generated/entity/type';
|
import { EntityReference } from 'generated/entity/type';
|
||||||
import { ReactNode } from 'react';
|
import { ReactNode } from 'react';
|
||||||
|
|
||||||
export type DataAssetsSourceMapping = {
|
export type DataAssetType =
|
||||||
[EntityType.TABLE]: Table;
|
| Table
|
||||||
[EntityType.TOPIC]: Topic;
|
| Topic
|
||||||
[EntityType.DASHBOARD]: Dashboard;
|
| Dashboard
|
||||||
[EntityType.PIPELINE]: Pipeline;
|
| Pipeline
|
||||||
[EntityType.MLMODEL]: Mlmodel;
|
| Mlmodel
|
||||||
[EntityType.CONTAINER]: Container;
|
| Container
|
||||||
};
|
| DashboardDataModel;
|
||||||
|
|
||||||
export type DataAssetsHeaderProps = {
|
export type DataAssetsHeaderProps = {
|
||||||
permissions: OperationPermission;
|
permissions: OperationPermission;
|
||||||
|
allowSoftDelete?: boolean;
|
||||||
|
isRecursiveDelete?: boolean;
|
||||||
onTierUpdate: (tier?: string) => Promise<void>;
|
onTierUpdate: (tier?: string) => Promise<void>;
|
||||||
onOwnerUpdate: (owner?: EntityReference) => Promise<void>;
|
onOwnerUpdate: (owner?: EntityReference) => Promise<void>;
|
||||||
onVersionClick: () => void;
|
onVersionClick?: () => void;
|
||||||
onFollowClick: () => Promise<void>;
|
onFollowClick?: () => Promise<void>;
|
||||||
onRestoreDataAsset: () => Promise<void>;
|
onRestoreDataAsset: () => Promise<void>;
|
||||||
onDisplayNameUpdate: (data: EntityName) => Promise<void>;
|
onDisplayNameUpdate: (data: EntityName) => Promise<void>;
|
||||||
} & (
|
} & (
|
||||||
@ -49,6 +52,7 @@ export type DataAssetsHeaderProps = {
|
|||||||
| DataAssetMlmodel
|
| DataAssetMlmodel
|
||||||
| DataAssetContainer
|
| DataAssetContainer
|
||||||
| DataAssetDashboardDataModel
|
| DataAssetDashboardDataModel
|
||||||
|
| DataAssetDatabase
|
||||||
);
|
);
|
||||||
|
|
||||||
export interface DataAssetTable {
|
export interface DataAssetTable {
|
||||||
@ -85,6 +89,11 @@ export interface DataAssetDashboardDataModel {
|
|||||||
entityType: EntityType.DASHBOARD_DATA_MODEL;
|
entityType: EntityType.DASHBOARD_DATA_MODEL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface DataAssetDatabase {
|
||||||
|
dataAsset: Database;
|
||||||
|
entityType: EntityType.DATABASE;
|
||||||
|
}
|
||||||
|
|
||||||
export interface DataAssetHeaderInfo {
|
export interface DataAssetHeaderInfo {
|
||||||
extraInfo: ReactNode;
|
extraInfo: ReactNode;
|
||||||
breadcrumbs: TitleBreadcrumbProps['titleLinks'];
|
breadcrumbs: TitleBreadcrumbProps['titleLinks'];
|
||||||
|
@ -128,7 +128,8 @@ export type EntityWithServices =
|
|||||||
| Pipeline
|
| Pipeline
|
||||||
| Mlmodel
|
| Mlmodel
|
||||||
| Container
|
| Container
|
||||||
| DashboardDataModel;
|
| DashboardDataModel
|
||||||
|
| Database;
|
||||||
|
|
||||||
export interface EntityDetailsObjectInterface {
|
export interface EntityDetailsObjectInterface {
|
||||||
details: SearchedDataProps['data'][number]['_source'];
|
details: SearchedDataProps['data'][number]['_source'];
|
||||||
|
@ -39,16 +39,6 @@ const mockDatabase = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockServiceData = {
|
|
||||||
id: 'bc13e95f-83ac-458a-9528-f4ca26657568',
|
|
||||||
type: 'databaseService',
|
|
||||||
name: 'bigquery_gcp',
|
|
||||||
description: '',
|
|
||||||
deleted: false,
|
|
||||||
href: 'http://localhost:8585/api/v1/services/databaseServices/bc13e95f-83ac-458a-9528-f4ca26657568',
|
|
||||||
jdbc: { driverClass: 'jdbc', connectionUrl: 'jdbc://localhost' },
|
|
||||||
};
|
|
||||||
|
|
||||||
const mockSchemaData = {
|
const mockSchemaData = {
|
||||||
data: [
|
data: [
|
||||||
{
|
{
|
||||||
@ -100,59 +90,6 @@ const mockSchemaData = {
|
|||||||
paging: { after: 'ZMbpLOqQQsREk_7DmEOr', total: 12 },
|
paging: { after: 'ZMbpLOqQQsREk_7DmEOr', total: 12 },
|
||||||
};
|
};
|
||||||
|
|
||||||
const mockAllFeeds = {
|
|
||||||
data: [
|
|
||||||
{
|
|
||||||
id: 'ac2e6128-9f23-4f28-acf8-31d50b06f8cc',
|
|
||||||
type: 'Task',
|
|
||||||
href: 'http://localhost:8585/api/v1/feed/ac2e6128-9f23-4f28-acf8-31d50b06f8cc',
|
|
||||||
threadTs: 1664445686074,
|
|
||||||
about: '<#E::table::sample_data.ecommerce_db.shopify.raw_order::tags>',
|
|
||||||
entityId: 'c514ca18-2ea4-44b1-aa06-0c66bc0cd355',
|
|
||||||
createdBy: 'bharatdussa',
|
|
||||||
updatedAt: 1664445691373,
|
|
||||||
updatedBy: 'bharatdussa',
|
|
||||||
resolved: false,
|
|
||||||
message: 'Update tags for table',
|
|
||||||
postsCount: 1,
|
|
||||||
posts: [
|
|
||||||
{
|
|
||||||
id: 'd497bea2-0bfe-4a9f-9ce6-d560129fef4a',
|
|
||||||
message: 'Resolved the Task with Tag(s) - PersonalData.Personal',
|
|
||||||
postTs: 1664445691368,
|
|
||||||
from: 'bharatdussa',
|
|
||||||
reactions: [],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
reactions: [],
|
|
||||||
task: {
|
|
||||||
id: 11,
|
|
||||||
type: 'UpdateTag',
|
|
||||||
assignees: [
|
|
||||||
{
|
|
||||||
id: 'f187364d-114c-4426-b941-baf6a15f70e4',
|
|
||||||
type: 'user',
|
|
||||||
name: 'bharatdussa',
|
|
||||||
fullyQualifiedName: 'bharatdussa',
|
|
||||||
displayName: 'Bharat Dussa',
|
|
||||||
deleted: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
status: 'Closed',
|
|
||||||
closedBy: 'bharatdussa',
|
|
||||||
closedAt: 1664445691340,
|
|
||||||
oldValue:
|
|
||||||
'[{"tagFQN":"PersonalData.Personal","description":"","source":"Classification","labelType":"Manual","state":"Suggested"},]',
|
|
||||||
suggestion:
|
|
||||||
'[{"tagFQN":"PersonalData.Personal","description":"","source":"Classification","labelType":"Manual","state":"Suggested"},]',
|
|
||||||
newValue:
|
|
||||||
'[{"tagFQN":"PersonalData.Personal","description":"","source":"Classification","labelType":"Manual","state":"Suggested"},]',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
paging: { after: 'MTY2NDQ0NDcyODY1MA==', total: 134 },
|
|
||||||
};
|
|
||||||
|
|
||||||
const mockFeedCount = {
|
const mockFeedCount = {
|
||||||
totalCount: 6,
|
totalCount: 6,
|
||||||
counts: [
|
counts: [
|
||||||
@ -209,6 +146,19 @@ jest.mock('react-router-dom', () => ({
|
|||||||
}),
|
}),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
jest.mock(
|
||||||
|
'components/ActivityFeed/ActivityFeedProvider/ActivityFeedProvider',
|
||||||
|
() => ({
|
||||||
|
useActivityFeedProvider: jest.fn().mockImplementation(() => ({
|
||||||
|
postFeed: jest.fn(),
|
||||||
|
deleteFeed: jest.fn(),
|
||||||
|
updateFeed: jest.fn(),
|
||||||
|
})),
|
||||||
|
__esModule: true,
|
||||||
|
default: 'ActivityFeedProvider',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
jest.mock('../../AppState', () => {
|
jest.mock('../../AppState', () => {
|
||||||
return jest.fn().mockReturnValue({
|
return jest.fn().mockReturnValue({
|
||||||
inPageSearchText: '',
|
inPageSearchText: '',
|
||||||
@ -229,60 +179,24 @@ jest.mock('rest/databaseAPI', () => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('rest/feedsAPI', () => ({
|
jest.mock('rest/feedsAPI', () => ({
|
||||||
getAllFeeds: jest
|
|
||||||
.fn()
|
|
||||||
.mockImplementation(() => Promise.resolve(mockAllFeeds)),
|
|
||||||
getFeedCount: jest
|
getFeedCount: jest
|
||||||
.fn()
|
.fn()
|
||||||
.mockImplementation(() => Promise.resolve(mockFeedCount)),
|
.mockImplementation(() => Promise.resolve(mockFeedCount)),
|
||||||
|
|
||||||
postFeedById: jest.fn().mockImplementation(() => Promise.resolve({})),
|
|
||||||
|
|
||||||
postThread: jest.fn().mockImplementation(() => Promise.resolve({})),
|
postThread: jest.fn().mockImplementation(() => Promise.resolve({})),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('rest/serviceAPI', () => ({
|
|
||||||
getServiceById: jest
|
|
||||||
.fn()
|
|
||||||
.mockImplementation(() => Promise.resolve({ data: mockServiceData })),
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock('../../utils/TableUtils', () => ({
|
jest.mock('../../utils/TableUtils', () => ({
|
||||||
getOwnerFromId: jest.fn().mockReturnValue({
|
|
||||||
name: 'owner',
|
|
||||||
id: 'string',
|
|
||||||
type: 'user',
|
|
||||||
}),
|
|
||||||
getUsagePercentile: jest.fn().mockReturnValue('Medium - 45th pctile'),
|
getUsagePercentile: jest.fn().mockReturnValue('Medium - 45th pctile'),
|
||||||
getTierTags: jest.fn().mockImplementation(() => ({})),
|
getTierTags: jest.fn().mockImplementation(() => ({})),
|
||||||
getTagsWithoutTier: jest.fn().mockImplementation(() => []),
|
getTagsWithoutTier: jest.fn().mockImplementation(() => []),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
jest.mock('../../utils/CommonUtils', () => ({
|
|
||||||
getCurrentUserId: jest
|
|
||||||
.fn()
|
|
||||||
.mockReturnValue('5d5ca778-8bee-4ea0-bcb6-b17d92f7ef96'),
|
|
||||||
isEven: jest.fn().mockReturnValue(true),
|
|
||||||
getEntityName: jest.fn().mockReturnValue('entityname'),
|
|
||||||
}));
|
|
||||||
|
|
||||||
jest.mock('components/Tag/Tags/tags', () => {
|
|
||||||
return jest.fn().mockReturnValue(<span>Tag</span>);
|
|
||||||
});
|
|
||||||
|
|
||||||
jest.mock('components/common/next-previous/NextPrevious', () => {
|
jest.mock('components/common/next-previous/NextPrevious', () => {
|
||||||
return jest.fn().mockReturnValue(<div>NextPrevious</div>);
|
return jest.fn().mockReturnValue(<div>NextPrevious</div>);
|
||||||
});
|
});
|
||||||
|
|
||||||
jest.mock(
|
jest.mock('components/Tag/TagsContainerV2/TagsContainerV2', () => {
|
||||||
'components/common/title-breadcrumb/title-breadcrumb.component',
|
return jest.fn().mockReturnValue(<div>TagsContainerV2</div>);
|
||||||
() => {
|
|
||||||
return jest.fn().mockReturnValue(<div>TitleBreadcrumb</div>);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
jest.mock('components/FeedEditor/FeedEditor', () => {
|
|
||||||
return jest.fn().mockReturnValue(<p>FeedEditor</p>);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
jest.mock('../../utils/TagsUtils', () => ({
|
jest.mock('../../utils/TagsUtils', () => ({
|
||||||
@ -309,37 +223,36 @@ jest.mock(
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
jest.mock('components/common/description/Description', () => {
|
jest.mock('components/common/description/DescriptionV1', () => {
|
||||||
return jest.fn().mockReturnValue(<p>Description</p>);
|
return jest.fn().mockReturnValue(<p>Description</p>);
|
||||||
});
|
});
|
||||||
|
|
||||||
jest.mock('components/common/EntitySummaryDetails/EntitySummaryDetails', () => {
|
|
||||||
return jest
|
|
||||||
.fn()
|
|
||||||
.mockReturnValue(
|
|
||||||
<p data-testid="entity-summary-details">EntitySummaryDetails component</p>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
jest.mock('components/common/DeleteWidget/DeleteWidgetModal', () => {
|
|
||||||
return jest
|
|
||||||
.fn()
|
|
||||||
.mockReturnValue(
|
|
||||||
<p data-testid="delete-entity">DeleteWidgetModal component</p>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
jest.mock('components/MyData/LeftSidebar/LeftSidebar.component', () =>
|
|
||||||
jest.fn().mockReturnValue(<p>Sidebar</p>)
|
|
||||||
);
|
|
||||||
|
|
||||||
jest.mock('components/containers/PageLayoutV1', () => {
|
jest.mock('components/containers/PageLayoutV1', () => {
|
||||||
return jest.fn().mockImplementation(({ children }) => children);
|
return jest.fn().mockImplementation(({ children }) => children);
|
||||||
});
|
});
|
||||||
|
|
||||||
jest.mock('components/Entity/EntityHeader/EntityHeader.component', () => ({
|
jest.mock(
|
||||||
EntityHeader: jest.fn().mockImplementation(() => <p>EntityHeader</p>),
|
'components/ActivityFeed/ActivityFeedTab/ActivityFeedTab.component',
|
||||||
}));
|
() => {
|
||||||
|
return jest.fn().mockReturnValue(<p>ActivityFeedTab</p>);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
jest.mock(
|
||||||
|
'components/ActivityFeed/ActivityThreadPanel/ActivityThreadPanel',
|
||||||
|
() => {
|
||||||
|
return jest.fn().mockReturnValue(<p>ActivityThreadPanel</p>);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
jest.mock(
|
||||||
|
'components/DataAssets/DataAssetsHeader/DataAssetsHeader.component',
|
||||||
|
() => ({
|
||||||
|
DataAssetsHeader: jest
|
||||||
|
.fn()
|
||||||
|
.mockImplementation(() => <p>DataAssetsHeader</p>),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
describe('Test DatabaseDetails page', () => {
|
describe('Test DatabaseDetails page', () => {
|
||||||
it('Component should render', async () => {
|
it('Component should render', async () => {
|
||||||
@ -347,11 +260,8 @@ describe('Test DatabaseDetails page', () => {
|
|||||||
wrapper: MemoryRouter,
|
wrapper: MemoryRouter,
|
||||||
});
|
});
|
||||||
|
|
||||||
const entityHeader = await findByText(container, 'EntityHeader');
|
const entityHeader = await findByText(container, 'DataAssetsHeader');
|
||||||
const descriptionContainer = await findByTestId(
|
const descriptionContainer = await findByText(container, 'Description');
|
||||||
container,
|
|
||||||
'description-container'
|
|
||||||
);
|
|
||||||
const databaseTable = await findByTestId(
|
const databaseTable = await findByTestId(
|
||||||
container,
|
container,
|
||||||
'database-databaseSchemas'
|
'database-databaseSchemas'
|
||||||
@ -421,11 +331,8 @@ describe('Test DatabaseDetails page', () => {
|
|||||||
wrapper: MemoryRouter,
|
wrapper: MemoryRouter,
|
||||||
});
|
});
|
||||||
|
|
||||||
const entityHeader = await findByText(container, 'EntityHeader');
|
const entityHeader = await findByText(container, 'DataAssetsHeader');
|
||||||
const descriptionContainer = await findByTestId(
|
const descriptionContainer = await findByText(container, 'Description');
|
||||||
container,
|
|
||||||
'description-container'
|
|
||||||
);
|
|
||||||
const databaseTable = await findByTestId(
|
const databaseTable = await findByTestId(
|
||||||
container,
|
container,
|
||||||
'database-databaseSchemas'
|
'database-databaseSchemas'
|
||||||
|
@ -11,16 +11,7 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import { Col, Row, Space, Switch, Table, Tabs, Typography } from 'antd';
|
||||||
Col,
|
|
||||||
Row,
|
|
||||||
Skeleton,
|
|
||||||
Space,
|
|
||||||
Switch,
|
|
||||||
Table,
|
|
||||||
Tabs,
|
|
||||||
Typography,
|
|
||||||
} from 'antd';
|
|
||||||
import { ColumnsType } from 'antd/lib/table';
|
import { ColumnsType } from 'antd/lib/table';
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import ActivityFeedProvider, {
|
import ActivityFeedProvider, {
|
||||||
@ -29,14 +20,11 @@ import ActivityFeedProvider, {
|
|||||||
import { ActivityFeedTab } from 'components/ActivityFeed/ActivityFeedTab/ActivityFeedTab.component';
|
import { ActivityFeedTab } from 'components/ActivityFeed/ActivityFeedTab/ActivityFeedTab.component';
|
||||||
import ActivityThreadPanel from 'components/ActivityFeed/ActivityThreadPanel/ActivityThreadPanel';
|
import ActivityThreadPanel from 'components/ActivityFeed/ActivityThreadPanel/ActivityThreadPanel';
|
||||||
import DescriptionV1 from 'components/common/description/DescriptionV1';
|
import DescriptionV1 from 'components/common/description/DescriptionV1';
|
||||||
import ManageButton from 'components/common/entityPageInfo/ManageButton/ManageButton';
|
|
||||||
import EntitySummaryDetails from 'components/common/EntitySummaryDetails/EntitySummaryDetails';
|
|
||||||
import ErrorPlaceHolder from 'components/common/error-with-placeholder/ErrorPlaceHolder';
|
import ErrorPlaceHolder from 'components/common/error-with-placeholder/ErrorPlaceHolder';
|
||||||
import NextPrevious from 'components/common/next-previous/NextPrevious';
|
import NextPrevious from 'components/common/next-previous/NextPrevious';
|
||||||
import RichTextEditorPreviewer from 'components/common/rich-text-editor/RichTextEditorPreviewer';
|
import RichTextEditorPreviewer from 'components/common/rich-text-editor/RichTextEditorPreviewer';
|
||||||
import { TitleBreadcrumbProps } from 'components/common/title-breadcrumb/title-breadcrumb.interface';
|
|
||||||
import PageLayoutV1 from 'components/containers/PageLayoutV1';
|
import PageLayoutV1 from 'components/containers/PageLayoutV1';
|
||||||
import { EntityHeader } from 'components/Entity/EntityHeader/EntityHeader.component';
|
import { DataAssetsHeader } from 'components/DataAssets/DataAssetsHeader/DataAssetsHeader.component';
|
||||||
import Loader from 'components/Loader/Loader';
|
import Loader from 'components/Loader/Loader';
|
||||||
import { EntityName } from 'components/Modals/EntityNameModal/EntityNameModal.interface';
|
import { EntityName } from 'components/Modals/EntityNameModal/EntityNameModal.interface';
|
||||||
import { usePermissionProvider } from 'components/PermissionProvider/PermissionProvider';
|
import { usePermissionProvider } from 'components/PermissionProvider/PermissionProvider';
|
||||||
@ -45,15 +33,15 @@ import {
|
|||||||
ResourceEntity,
|
ResourceEntity,
|
||||||
} from 'components/PermissionProvider/PermissionProvider.interface';
|
} from 'components/PermissionProvider/PermissionProvider.interface';
|
||||||
import TabsLabel from 'components/TabsLabel/TabsLabel.component';
|
import TabsLabel from 'components/TabsLabel/TabsLabel.component';
|
||||||
import TagsContainer from 'components/Tag/TagsContainer/tags-container';
|
import TagsContainerV2 from 'components/Tag/TagsContainerV2/TagsContainerV2';
|
||||||
import { ERROR_PLACEHOLDER_TYPE } from 'enums/common.enum';
|
import { ERROR_PLACEHOLDER_TYPE } from 'enums/common.enum';
|
||||||
import { compare, Operation } from 'fast-json-patch';
|
import { compare, Operation } from 'fast-json-patch';
|
||||||
import { LabelType } from 'generated/entity/data/table';
|
import { LabelType } from 'generated/entity/data/table';
|
||||||
import { Include } from 'generated/type/include';
|
import { Include } from 'generated/type/include';
|
||||||
import { State } from 'generated/type/tagLabel';
|
import { State, TagSource } from 'generated/type/tagLabel';
|
||||||
import { isEmpty, isNil, isUndefined, startCase } from 'lodash';
|
import { isEmpty, isNil, isUndefined } from 'lodash';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import { EntityTags, ExtraInfo, TagOption } from 'Models';
|
import { EntityTags } from 'Models';
|
||||||
import React, {
|
import React, {
|
||||||
FunctionComponent,
|
FunctionComponent,
|
||||||
useCallback,
|
useCallback,
|
||||||
@ -70,23 +58,17 @@ import {
|
|||||||
patchDatabaseDetails,
|
patchDatabaseDetails,
|
||||||
} from 'rest/databaseAPI';
|
} from 'rest/databaseAPI';
|
||||||
import { getFeedCount, postThread } from 'rest/feedsAPI';
|
import { getFeedCount, postThread } from 'rest/feedsAPI';
|
||||||
import { fetchTagsAndGlossaryTerms } from 'utils/TagsUtils';
|
|
||||||
import { default as appState } from '../../AppState';
|
import { default as appState } from '../../AppState';
|
||||||
import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants';
|
import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants';
|
||||||
import {
|
import {
|
||||||
getDatabaseDetailsPath,
|
getDatabaseDetailsPath,
|
||||||
getDatabaseSchemaDetailsPath,
|
getDatabaseSchemaDetailsPath,
|
||||||
getExplorePath,
|
getExplorePath,
|
||||||
getServiceDetailsPath,
|
|
||||||
getTeamAndUserDetailsPath,
|
|
||||||
PAGE_SIZE,
|
PAGE_SIZE,
|
||||||
pagingObject,
|
pagingObject,
|
||||||
} from '../../constants/constants';
|
} from '../../constants/constants';
|
||||||
import { EntityField } from '../../constants/Feeds.constants';
|
import { EntityField } from '../../constants/Feeds.constants';
|
||||||
import { GlobalSettingsMenuCategory } from '../../constants/GlobalSettings.constants';
|
import { EntityTabs, EntityType } from '../../enums/entity.enum';
|
||||||
import { EntityInfo, EntityTabs, EntityType } from '../../enums/entity.enum';
|
|
||||||
import { ServiceCategory } from '../../enums/service.enum';
|
|
||||||
import { OwnerType } from '../../enums/user.enum';
|
|
||||||
import { CreateThread } from '../../generated/api/feed/createThread';
|
import { CreateThread } from '../../generated/api/feed/createThread';
|
||||||
import { Database } from '../../generated/entity/data/database';
|
import { Database } from '../../generated/entity/data/database';
|
||||||
import { DatabaseSchema } from '../../generated/entity/data/databaseSchema';
|
import { DatabaseSchema } from '../../generated/entity/data/databaseSchema';
|
||||||
@ -94,14 +76,13 @@ import { EntityReference } from '../../generated/entity/teams/user';
|
|||||||
import { UsageDetails } from '../../generated/type/entityUsage';
|
import { UsageDetails } from '../../generated/type/entityUsage';
|
||||||
import { Paging } from '../../generated/type/paging';
|
import { Paging } from '../../generated/type/paging';
|
||||||
import { EntityFieldThreadCount } from '../../interface/feed.interface';
|
import { EntityFieldThreadCount } from '../../interface/feed.interface';
|
||||||
import { getEntityFeedLink, getEntityName } from '../../utils/EntityUtils';
|
import {
|
||||||
|
getEntityFeedLink,
|
||||||
|
getEntityName,
|
||||||
|
getEntityThreadLink,
|
||||||
|
} from '../../utils/EntityUtils';
|
||||||
import { getEntityFieldThreadCounts } from '../../utils/FeedUtils';
|
import { getEntityFieldThreadCounts } from '../../utils/FeedUtils';
|
||||||
import { DEFAULT_ENTITY_PERMISSION } from '../../utils/PermissionsUtils';
|
import { DEFAULT_ENTITY_PERMISSION } from '../../utils/PermissionsUtils';
|
||||||
import { getSettingPath } from '../../utils/RouterUtils';
|
|
||||||
import {
|
|
||||||
getServiceRouteFromServiceType,
|
|
||||||
serviceTypeLogo,
|
|
||||||
} from '../../utils/ServiceUtils';
|
|
||||||
import { getErrorText } from '../../utils/StringsUtils';
|
import { getErrorText } from '../../utils/StringsUtils';
|
||||||
import {
|
import {
|
||||||
getTagsWithoutTier,
|
getTagsWithoutTier,
|
||||||
@ -113,16 +94,13 @@ import { showErrorToast } from '../../utils/ToastUtils';
|
|||||||
const DatabaseDetails: FunctionComponent = () => {
|
const DatabaseDetails: FunctionComponent = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const { postFeed, deleteFeed, updateFeed } = useActivityFeedProvider();
|
const { postFeed, deleteFeed, updateFeed } = useActivityFeedProvider();
|
||||||
const [slashedDatabaseName, setSlashedDatabaseName] = useState<
|
|
||||||
TitleBreadcrumbProps['titleLinks']
|
|
||||||
>([]);
|
|
||||||
const { getEntityPermissionByFqn } = usePermissionProvider();
|
const { getEntityPermissionByFqn } = usePermissionProvider();
|
||||||
|
|
||||||
const { databaseFQN, tab: activeTab = EntityTabs.SCHEMA } =
|
const { databaseFQN, tab: activeTab = EntityTabs.SCHEMA } =
|
||||||
useParams<{ databaseFQN: string; tab: EntityTabs }>();
|
useParams<{ databaseFQN: string; tab: EntityTabs }>();
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
const [showDeletedSchemas, setShowDeletedSchemas] = useState<boolean>(false);
|
const [showDeletedSchemas, setShowDeletedSchemas] = useState<boolean>(false);
|
||||||
const [database, setDatabase] = useState<Database>();
|
const [database, setDatabase] = useState<Database>({} as Database);
|
||||||
const [serviceType, setServiceType] = useState<string>();
|
const [serviceType, setServiceType] = useState<string>();
|
||||||
const [schemaData, setSchemaData] = useState<DatabaseSchema[]>([]);
|
const [schemaData, setSchemaData] = useState<DatabaseSchema[]>([]);
|
||||||
const [schemaDataLoading, setSchemaDataLoading] = useState<boolean>(true);
|
const [schemaDataLoading, setSchemaDataLoading] = useState<boolean>(true);
|
||||||
@ -148,9 +126,6 @@ const DatabaseDetails: FunctionComponent = () => {
|
|||||||
|
|
||||||
const [threadLink, setThreadLink] = useState<string>('');
|
const [threadLink, setThreadLink] = useState<string>('');
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
const [isEditable, setIsEditable] = useState<boolean>(false);
|
|
||||||
const [tagList, setTagList] = useState<Array<TagOption>>([]);
|
|
||||||
const [isTagLoading, setIsTagLoading] = useState<boolean>(false);
|
|
||||||
|
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const isMounting = useRef(true);
|
const isMounting = useRef(true);
|
||||||
@ -158,8 +133,6 @@ const DatabaseDetails: FunctionComponent = () => {
|
|||||||
const tier = getTierTags(database?.tags ?? []);
|
const tier = getTierTags(database?.tags ?? []);
|
||||||
const tags = getTagsWithoutTier(database?.tags ?? []);
|
const tags = getTagsWithoutTier(database?.tags ?? []);
|
||||||
|
|
||||||
const deleted = database?.deleted;
|
|
||||||
|
|
||||||
const [databasePermission, setDatabasePermission] =
|
const [databasePermission, setDatabasePermission] =
|
||||||
useState<OperationPermission>(DEFAULT_ENTITY_PERMISSION);
|
useState<OperationPermission>(DEFAULT_ENTITY_PERMISSION);
|
||||||
|
|
||||||
@ -178,57 +151,6 @@ const DatabaseDetails: FunctionComponent = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const tabs = useMemo(() => {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
label: (
|
|
||||||
<TabsLabel
|
|
||||||
count={databaseSchemaInstanceCount}
|
|
||||||
id={EntityTabs.SCHEMA}
|
|
||||||
isActive={activeTab === EntityTabs.SCHEMA}
|
|
||||||
name={t('label.schema-plural')}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
key: EntityTabs.SCHEMA,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: (
|
|
||||||
<TabsLabel
|
|
||||||
count={feedCount}
|
|
||||||
id={EntityTabs.ACTIVITY_FEED}
|
|
||||||
isActive={activeTab === EntityTabs.ACTIVITY_FEED}
|
|
||||||
name={t('label.activity-feed-plural')}
|
|
||||||
/>
|
|
||||||
),
|
|
||||||
key: EntityTabs.ACTIVITY_FEED,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
}, [activeTab, databaseSchemaInstanceCount, feedCount]);
|
|
||||||
|
|
||||||
const extraInfo: Array<ExtraInfo> = [
|
|
||||||
{
|
|
||||||
key: EntityInfo.OWNER,
|
|
||||||
value:
|
|
||||||
database?.owner?.type === 'team'
|
|
||||||
? getTeamAndUserDetailsPath(
|
|
||||||
database?.owner?.displayName || database?.owner?.name || ''
|
|
||||||
)
|
|
||||||
: database?.owner?.displayName || database?.owner?.name || '',
|
|
||||||
placeholderText:
|
|
||||||
database?.owner?.displayName || database?.owner?.name || '',
|
|
||||||
isLink: database?.owner?.type === 'team',
|
|
||||||
openInNewTab: false,
|
|
||||||
profileName:
|
|
||||||
database?.owner?.type === OwnerType.USER
|
|
||||||
? database?.owner?.name
|
|
||||||
: undefined,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
key: EntityInfo.TIER,
|
|
||||||
value: tier?.tagFQN ? tier.tagFQN.split(FQN_SEPARATOR_CHAR)[1] : '',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const fetchDatabaseSchemas = (pagingObj?: string) => {
|
const fetchDatabaseSchemas = (pagingObj?: string) => {
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
setSchemaDataLoading(true);
|
setSchemaDataLoading(true);
|
||||||
@ -302,34 +224,12 @@ const DatabaseDetails: FunctionComponent = () => {
|
|||||||
getDatabaseDetailsByFQN(databaseFQN, ['owner', 'tags'])
|
getDatabaseDetailsByFQN(databaseFQN, ['owner', 'tags'])
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res) {
|
if (res) {
|
||||||
const { description, id, name, service, serviceType } = res;
|
const { description, id, name, serviceType } = res;
|
||||||
setDatabase(res);
|
setDatabase(res);
|
||||||
setDescription(description ?? '');
|
setDescription(description ?? '');
|
||||||
setDatabaseId(id ?? '');
|
setDatabaseId(id ?? '');
|
||||||
setDatabaseName(name);
|
setDatabaseName(name);
|
||||||
|
|
||||||
setServiceType(serviceType);
|
setServiceType(serviceType);
|
||||||
|
|
||||||
setSlashedDatabaseName([
|
|
||||||
{
|
|
||||||
name: startCase(ServiceCategory.DATABASE_SERVICES),
|
|
||||||
url: getSettingPath(
|
|
||||||
GlobalSettingsMenuCategory.SERVICES,
|
|
||||||
getServiceRouteFromServiceType(
|
|
||||||
ServiceCategory.DATABASE_SERVICES
|
|
||||||
)
|
|
||||||
),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: getEntityName(service),
|
|
||||||
url: service.name
|
|
||||||
? getServiceDetailsPath(
|
|
||||||
service.name,
|
|
||||||
ServiceCategory.DATABASE_SERVICES
|
|
||||||
)
|
|
||||||
: '',
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
fetchDatabaseSchemasAndDBTModels();
|
fetchDatabaseSchemasAndDBTModels();
|
||||||
} else {
|
} else {
|
||||||
throw t('server.unexpected-response');
|
throw t('server.unexpected-response');
|
||||||
@ -431,13 +331,13 @@ const DatabaseDetails: FunctionComponent = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleUpdateOwner = useCallback(
|
const handleUpdateOwner = useCallback(
|
||||||
(owner: Database['owner']) => {
|
async (owner: Database['owner']) => {
|
||||||
const updatedData = {
|
const updatedData = {
|
||||||
...database,
|
...database,
|
||||||
owner: owner ? { ...database?.owner, ...owner } : undefined,
|
owner: owner ? { ...database?.owner, ...owner } : undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
settingsUpdateHandler(updatedData as Database);
|
await settingsUpdateHandler(updatedData as Database);
|
||||||
},
|
},
|
||||||
[database, database?.owner, settingsUpdateHandler]
|
[database, database?.owner, settingsUpdateHandler]
|
||||||
);
|
);
|
||||||
@ -579,51 +479,19 @@ const DatabaseDetails: FunctionComponent = () => {
|
|||||||
return settingsUpdateHandler(updatedTableDetails);
|
return settingsUpdateHandler(updatedTableDetails);
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchTags = async () => {
|
|
||||||
setIsTagLoading(true);
|
|
||||||
try {
|
|
||||||
const tags = await fetchTagsAndGlossaryTerms();
|
|
||||||
setTagList(tags);
|
|
||||||
} catch (error) {
|
|
||||||
setTagList([]);
|
|
||||||
} finally {
|
|
||||||
setIsTagLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const isTagEditable =
|
|
||||||
databasePermission.EditTags || databasePermission.EditAll;
|
|
||||||
|
|
||||||
const selectedTags = useMemo(() => {
|
|
||||||
return tier?.tagFQN
|
|
||||||
? [
|
|
||||||
...tags.map((tag) => ({
|
|
||||||
...tag,
|
|
||||||
isRemovable: true,
|
|
||||||
})),
|
|
||||||
{ tagFQN: tier.tagFQN, isRemovable: false },
|
|
||||||
]
|
|
||||||
: [
|
|
||||||
...tags.map((tag) => ({
|
|
||||||
...tag,
|
|
||||||
isRemovable: true,
|
|
||||||
})),
|
|
||||||
] ?? [];
|
|
||||||
}, [tier, tags]);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Formulates updated tags and updates table entity data for API call
|
* Formulates updated tags and updates table entity data for API call
|
||||||
* @param selectedTags
|
* @param selectedTags
|
||||||
*/
|
*/
|
||||||
const onTagUpdate = (selectedTags?: Array<EntityTags>) => {
|
const onTagUpdate = async (selectedTags?: Array<EntityTags>) => {
|
||||||
if (selectedTags) {
|
if (selectedTags) {
|
||||||
const updatedTags = [...(tier ? [tier] : []), ...selectedTags];
|
const updatedTags = [...(tier ? [tier] : []), ...selectedTags];
|
||||||
const updatedTable = { ...database, tags: updatedTags };
|
const updatedTable = { ...database, tags: updatedTags };
|
||||||
settingsUpdateHandler(updatedTable as Database);
|
await settingsUpdateHandler(updatedTable as Database);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleTagSelection = (selectedTags?: Array<EntityTags>) => {
|
const handleTagSelection = async (selectedTags: EntityTags[]) => {
|
||||||
if (selectedTags) {
|
if (selectedTags) {
|
||||||
const prevTags =
|
const prevTags =
|
||||||
tags?.filter((tag) =>
|
tags?.filter((tag) =>
|
||||||
@ -643,9 +511,8 @@ const DatabaseDetails: FunctionComponent = () => {
|
|||||||
source: tag.source,
|
source: tag.source,
|
||||||
tagFQN: tag.tagFQN,
|
tagFQN: tag.tagFQN,
|
||||||
}));
|
}));
|
||||||
onTagUpdate([...prevTags, ...newTags]);
|
await onTagUpdate([...prevTags, ...newTags]);
|
||||||
}
|
}
|
||||||
setIsEditable(false);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const databaseTable = useMemo(() => {
|
const databaseTable = useMemo(() => {
|
||||||
@ -659,10 +526,6 @@ const DatabaseDetails: FunctionComponent = () => {
|
|||||||
columns={tableColumn}
|
columns={tableColumn}
|
||||||
data-testid="database-databaseSchemas"
|
data-testid="database-databaseSchemas"
|
||||||
dataSource={schemaData}
|
dataSource={schemaData}
|
||||||
loading={{
|
|
||||||
spinning: schemaDataLoading,
|
|
||||||
indicator: <Loader size="small" />,
|
|
||||||
}}
|
|
||||||
pagination={false}
|
pagination={false}
|
||||||
rowKey="id"
|
rowKey="id"
|
||||||
size="small"
|
size="small"
|
||||||
@ -693,11 +556,136 @@ const DatabaseDetails: FunctionComponent = () => {
|
|||||||
databaseSchemaPagingHandler,
|
databaseSchemaPagingHandler,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
const tabs = useMemo(
|
||||||
|
() => [
|
||||||
|
{
|
||||||
|
label: (
|
||||||
|
<TabsLabel
|
||||||
|
count={databaseSchemaInstanceCount}
|
||||||
|
id={EntityTabs.SCHEMA}
|
||||||
|
isActive={activeTab === EntityTabs.SCHEMA}
|
||||||
|
name={t('label.schema-plural')}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
key: EntityTabs.SCHEMA,
|
||||||
|
children: (
|
||||||
|
<Row gutter={[0, 16]} wrap={false}>
|
||||||
|
<Col className="p-t-sm m-l-lg" flex="auto">
|
||||||
|
<div className="d-flex flex-col gap-4">
|
||||||
|
<DescriptionV1
|
||||||
|
description={description}
|
||||||
|
entityFieldThreads={getEntityFieldThreadCounts(
|
||||||
|
EntityField.DESCRIPTION,
|
||||||
|
entityFieldThreadCount
|
||||||
|
)}
|
||||||
|
entityFqn={databaseFQN}
|
||||||
|
entityName={databaseName}
|
||||||
|
entityType={EntityType.DATABASE}
|
||||||
|
hasEditAccess={
|
||||||
|
databasePermission.EditDescription ||
|
||||||
|
databasePermission.EditAll
|
||||||
|
}
|
||||||
|
isEdit={isEdit}
|
||||||
|
onCancel={onCancel}
|
||||||
|
onDescriptionEdit={onDescriptionEdit}
|
||||||
|
onDescriptionUpdate={onDescriptionUpdate}
|
||||||
|
onThreadLinkSelect={onThreadLinkSelect}
|
||||||
|
/>
|
||||||
|
<Row justify="end">
|
||||||
|
<Col>
|
||||||
|
<Switch
|
||||||
|
checked={showDeletedSchemas}
|
||||||
|
data-testid="show-deleted"
|
||||||
|
onClick={setShowDeletedSchemas}
|
||||||
|
/>
|
||||||
|
<Typography.Text className="m-l-xs">
|
||||||
|
{t('label.deleted')}
|
||||||
|
</Typography.Text>{' '}
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
|
||||||
|
{databaseTable}
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col
|
||||||
|
className="entity-tag-right-panel-container"
|
||||||
|
data-testid="entity-right-panel"
|
||||||
|
flex="320px">
|
||||||
|
<Space className="w-full" direction="vertical" size="large">
|
||||||
|
<TagsContainerV2
|
||||||
|
entityFqn={databaseFQN}
|
||||||
|
entityThreadLink={getEntityThreadLink(entityFieldThreadCount)}
|
||||||
|
entityType={EntityType.DATABASE}
|
||||||
|
permission={
|
||||||
|
databasePermission.EditDescription ||
|
||||||
|
(databasePermission.EditAll && !database?.deleted)
|
||||||
|
}
|
||||||
|
selectedTags={tags}
|
||||||
|
tagType={TagSource.Classification}
|
||||||
|
onSelectionChange={handleTagSelection}
|
||||||
|
onThreadLinkSelect={onThreadLinkSelect}
|
||||||
|
/>
|
||||||
|
<TagsContainerV2
|
||||||
|
entityFqn={databaseFQN}
|
||||||
|
entityThreadLink={getEntityThreadLink(entityFieldThreadCount)}
|
||||||
|
entityType={EntityType.DATABASE}
|
||||||
|
permission={
|
||||||
|
databasePermission.EditDescription ||
|
||||||
|
(databasePermission.EditAll && !database?.deleted)
|
||||||
|
}
|
||||||
|
selectedTags={tags}
|
||||||
|
tagType={TagSource.Glossary}
|
||||||
|
onSelectionChange={handleTagSelection}
|
||||||
|
onThreadLinkSelect={onThreadLinkSelect}
|
||||||
|
/>
|
||||||
|
</Space>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: (
|
||||||
|
<TabsLabel
|
||||||
|
count={feedCount}
|
||||||
|
id={EntityTabs.ACTIVITY_FEED}
|
||||||
|
isActive={activeTab === EntityTabs.ACTIVITY_FEED}
|
||||||
|
name={t('label.activity-feed-plural')}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
key: EntityTabs.ACTIVITY_FEED,
|
||||||
|
children: (
|
||||||
|
<ActivityFeedProvider>
|
||||||
|
<ActivityFeedTab
|
||||||
|
entityType={EntityType.DATABASE}
|
||||||
|
fqn={database?.fullyQualifiedName ?? ''}
|
||||||
|
onFeedUpdate={getEntityFeedCount}
|
||||||
|
/>
|
||||||
|
</ActivityFeedProvider>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
tags,
|
||||||
|
isEdit,
|
||||||
|
database,
|
||||||
|
description,
|
||||||
|
databaseName,
|
||||||
|
entityFieldThreadCount,
|
||||||
|
databaseFQN,
|
||||||
|
activeTab,
|
||||||
|
databaseTable,
|
||||||
|
databasePermission,
|
||||||
|
databaseSchemaInstanceCount,
|
||||||
|
feedCount,
|
||||||
|
showDeletedSchemas,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchDatabaseSchemas();
|
fetchDatabaseSchemas();
|
||||||
}, [showDeletedSchemas]);
|
}, [showDeletedSchemas]);
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading || isDatabaseDetailsLoading) {
|
||||||
return <Loader />;
|
return <Loader />;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -709,217 +697,53 @@ const DatabaseDetails: FunctionComponent = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
if (!(databasePermission.ViewAll || databasePermission.ViewBasic)) {
|
||||||
<>
|
return <ErrorPlaceHolder type={ERROR_PLACEHOLDER_TYPE.PERMISSION} />;
|
||||||
{databasePermission.ViewAll || databasePermission.ViewBasic ? (
|
}
|
||||||
<PageLayoutV1
|
|
||||||
pageTitle={t('label.entity-detail-plural', {
|
|
||||||
entity: getEntityName(database),
|
|
||||||
})}>
|
|
||||||
<Row className="page-container">
|
|
||||||
{isDatabaseDetailsLoading ? (
|
|
||||||
<Skeleton
|
|
||||||
active
|
|
||||||
paragraph={{
|
|
||||||
rows: 3,
|
|
||||||
width: ['20%', '80%', '60%'],
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<Col span={24}>
|
|
||||||
{database && (
|
|
||||||
<Row wrap={false}>
|
|
||||||
<Col flex="auto">
|
|
||||||
<EntityHeader
|
|
||||||
breadcrumb={slashedDatabaseName}
|
|
||||||
entityData={database}
|
|
||||||
entityType={EntityType.DATABASE}
|
|
||||||
icon={
|
|
||||||
<img
|
|
||||||
className="h-8"
|
|
||||||
src={serviceTypeLogo(serviceType ?? '')}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
serviceName={database.service.name ?? ''}
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
<Col flex="30px">
|
|
||||||
<ManageButton
|
|
||||||
isRecursiveDelete
|
|
||||||
allowSoftDelete={false}
|
|
||||||
canDelete={databasePermission.Delete}
|
|
||||||
displayName={database.displayName}
|
|
||||||
editDisplayNamePermission={
|
|
||||||
databasePermission.EditAll ||
|
|
||||||
databasePermission.EditDisplayName
|
|
||||||
}
|
|
||||||
entityFQN={databaseFQN}
|
|
||||||
entityId={databaseId}
|
|
||||||
entityName={databaseName}
|
|
||||||
entityType={EntityType.DATABASE}
|
|
||||||
onEditDisplayName={handleUpdateDisplayName}
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
)}
|
|
||||||
<Col className="m-t-xs" span={24}>
|
|
||||||
<Space wrap align="center" data-testid="extrainfo" size={4}>
|
|
||||||
{extraInfo.map((info, index) => (
|
|
||||||
<span
|
|
||||||
className="d-flex tw-items-center"
|
|
||||||
data-testid={info.key || `info${index}`}
|
|
||||||
key={index}>
|
|
||||||
<EntitySummaryDetails
|
|
||||||
currentOwner={database?.owner}
|
|
||||||
data={info}
|
|
||||||
tier={getTierTags(database?.tags ?? [])}
|
|
||||||
updateOwner={
|
|
||||||
databasePermission.EditOwner ||
|
|
||||||
databasePermission.EditAll
|
|
||||||
? handleUpdateOwner
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
updateTier={
|
|
||||||
databasePermission.EditTags ||
|
|
||||||
databasePermission.EditAll
|
|
||||||
? handleUpdateTier
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
{extraInfo.length !== 1 &&
|
|
||||||
index < extraInfo.length - 1 ? (
|
|
||||||
<span className="tw-mx-1.5 tw-inline-block tw-text-gray-400">
|
|
||||||
{t('label.pipe-symbol')}
|
|
||||||
</span>
|
|
||||||
) : null}
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
</Space>
|
|
||||||
</Col>
|
|
||||||
<Col className="m-t-xs" span={24}>
|
|
||||||
<Space
|
|
||||||
wrap
|
|
||||||
align="center"
|
|
||||||
data-testid="entity-tags"
|
|
||||||
size={6}
|
|
||||||
onClick={() => {
|
|
||||||
if (isTagEditable) {
|
|
||||||
// Fetch tags and terms only once
|
|
||||||
if (tagList.length === 0) {
|
|
||||||
fetchTags();
|
|
||||||
}
|
|
||||||
setIsEditable(true);
|
|
||||||
}
|
|
||||||
}}>
|
|
||||||
{!deleted && (
|
|
||||||
<TagsContainer
|
|
||||||
className="w-min-20"
|
|
||||||
dropDownHorzPosRight={false}
|
|
||||||
editable={isEditable}
|
|
||||||
isLoading={isTagLoading}
|
|
||||||
selectedTags={selectedTags}
|
|
||||||
showAddTagButton={
|
|
||||||
isTagEditable && isEmpty(selectedTags)
|
|
||||||
}
|
|
||||||
showEditTagButton={isTagEditable}
|
|
||||||
size="small"
|
|
||||||
tagList={tagList}
|
|
||||||
onCancel={() => {
|
|
||||||
handleTagSelection();
|
|
||||||
}}
|
|
||||||
onSelectionChange={(tags) => {
|
|
||||||
handleTagSelection(tags);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Space>
|
|
||||||
</Col>
|
|
||||||
</Col>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<Col span={24}>
|
return (
|
||||||
<Row className="m-t-md">
|
<PageLayoutV1
|
||||||
<Col span={24}>
|
className="bg-white"
|
||||||
<Tabs
|
pageTitle={t('label.entity-detail-plural', {
|
||||||
activeKey={activeTab ?? EntityTabs.SCHEMA}
|
entity: getEntityName(database),
|
||||||
items={tabs}
|
})}>
|
||||||
onChange={activeTabHandler}
|
<Row gutter={[0, 12]}>
|
||||||
/>
|
<Col className="p-x-lg" span={24}>
|
||||||
</Col>
|
<DataAssetsHeader
|
||||||
<Col className="p-y-md" span={24}>
|
isRecursiveDelete
|
||||||
{activeTab === EntityTabs.SCHEMA && (
|
allowSoftDelete={false}
|
||||||
<>
|
dataAsset={database}
|
||||||
<Row gutter={[16, 16]}>
|
entityType={EntityType.DATABASE}
|
||||||
<Col data-testid="description-container" span={24}>
|
permissions={databasePermission}
|
||||||
<DescriptionV1
|
onDisplayNameUpdate={handleUpdateDisplayName}
|
||||||
description={description}
|
onOwnerUpdate={handleUpdateOwner}
|
||||||
entityFieldThreads={getEntityFieldThreadCounts(
|
onRestoreDataAsset={() => Promise.resolve()}
|
||||||
EntityField.DESCRIPTION,
|
onTierUpdate={handleUpdateTier}
|
||||||
entityFieldThreadCount
|
/>
|
||||||
)}
|
</Col>
|
||||||
entityFqn={databaseFQN}
|
<Col span={24}>
|
||||||
entityName={databaseName}
|
<Tabs
|
||||||
entityType={EntityType.DATABASE}
|
activeKey={activeTab ?? EntityTabs.SCHEMA}
|
||||||
hasEditAccess={
|
className="entity-details-page-tabs"
|
||||||
databasePermission.EditDescription ||
|
data-testid="tabs"
|
||||||
databasePermission.EditAll
|
items={tabs}
|
||||||
}
|
onChange={activeTabHandler}
|
||||||
isEdit={isEdit}
|
/>
|
||||||
onCancel={onCancel}
|
</Col>
|
||||||
onDescriptionEdit={onDescriptionEdit}
|
|
||||||
onDescriptionUpdate={onDescriptionUpdate}
|
{threadLink ? (
|
||||||
onThreadLinkSelect={onThreadLinkSelect}
|
<ActivityThreadPanel
|
||||||
/>
|
createThread={createThread}
|
||||||
</Col>
|
deletePostHandler={deleteFeed}
|
||||||
<Col span={24}>
|
open={Boolean(threadLink)}
|
||||||
<Row justify="end">
|
postFeedHandler={postFeed}
|
||||||
<Col>
|
threadLink={threadLink}
|
||||||
<Switch
|
updateThreadHandler={updateFeed}
|
||||||
checked={showDeletedSchemas}
|
onCancel={onThreadPanelClose}
|
||||||
data-testid="show-deleted"
|
/>
|
||||||
onClick={setShowDeletedSchemas}
|
) : null}
|
||||||
/>
|
</Row>
|
||||||
<Typography.Text className="m-l-xs">
|
</PageLayoutV1>
|
||||||
{t('label.deleted')}
|
|
||||||
</Typography.Text>{' '}
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</Col>
|
|
||||||
{databaseTable}
|
|
||||||
</Row>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{activeTab === EntityTabs.ACTIVITY_FEED && (
|
|
||||||
<ActivityFeedProvider>
|
|
||||||
<ActivityFeedTab
|
|
||||||
entityType={EntityType.DATABASE}
|
|
||||||
fqn={database?.fullyQualifiedName ?? ''}
|
|
||||||
onFeedUpdate={getEntityFeedCount}
|
|
||||||
/>
|
|
||||||
</ActivityFeedProvider>
|
|
||||||
)}
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</Col>
|
|
||||||
<Col span={24}>
|
|
||||||
{threadLink ? (
|
|
||||||
<ActivityThreadPanel
|
|
||||||
createThread={createThread}
|
|
||||||
deletePostHandler={deleteFeed}
|
|
||||||
open={Boolean(threadLink)}
|
|
||||||
postFeedHandler={postFeed}
|
|
||||||
threadLink={threadLink}
|
|
||||||
updateThreadHandler={updateFeed}
|
|
||||||
onCancel={onThreadPanelClose}
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</PageLayoutV1>
|
|
||||||
) : (
|
|
||||||
<ErrorPlaceHolder type={ERROR_PLACEHOLDER_TYPE.PERMISSION} />
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -28,10 +28,12 @@ import {
|
|||||||
SourceType,
|
SourceType,
|
||||||
} from 'components/searched-data/SearchedData.interface';
|
} from 'components/searched-data/SearchedData.interface';
|
||||||
import { EntityField } from 'constants/Feeds.constants';
|
import { EntityField } from 'constants/Feeds.constants';
|
||||||
|
import { GlobalSettingsMenuCategory } from 'constants/GlobalSettings.constants';
|
||||||
import { ExplorePageTabs } from 'enums/Explore.enum';
|
import { ExplorePageTabs } from 'enums/Explore.enum';
|
||||||
import { Tag } from 'generated/entity/classification/tag';
|
import { Tag } from 'generated/entity/classification/tag';
|
||||||
import { Container } from 'generated/entity/data/container';
|
import { Container } from 'generated/entity/data/container';
|
||||||
import { DashboardDataModel } from 'generated/entity/data/dashboardDataModel';
|
import { DashboardDataModel } from 'generated/entity/data/dashboardDataModel';
|
||||||
|
import { Database } from 'generated/entity/data/database';
|
||||||
import { GlossaryTerm } from 'generated/entity/data/glossaryTerm';
|
import { GlossaryTerm } from 'generated/entity/data/glossaryTerm';
|
||||||
import { Mlmodel } from 'generated/entity/data/mlmodel';
|
import { Mlmodel } from 'generated/entity/data/mlmodel';
|
||||||
import { Topic } from 'generated/entity/data/topic';
|
import { Topic } from 'generated/entity/data/topic';
|
||||||
@ -79,7 +81,8 @@ import {
|
|||||||
} from './CommonUtils';
|
} from './CommonUtils';
|
||||||
import { getEntityFieldThreadCounts } from './FeedUtils';
|
import { getEntityFieldThreadCounts } from './FeedUtils';
|
||||||
import Fqn from './Fqn';
|
import Fqn from './Fqn';
|
||||||
import { getGlossaryPath } from './RouterUtils';
|
import { getGlossaryPath, getSettingPath } from './RouterUtils';
|
||||||
|
import { getServiceRouteFromServiceType } from './ServiceUtils';
|
||||||
import {
|
import {
|
||||||
getDataTypeString,
|
getDataTypeString,
|
||||||
getTierFromTableTags,
|
getTierFromTableTags,
|
||||||
@ -985,7 +988,10 @@ export const getBreadcrumbForEntitiesWithServiceOnly = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const getEntityBreadcrumbs = (
|
export const getEntityBreadcrumbs = (
|
||||||
entity: SearchedDataProps['data'][number]['_source'] | DashboardDataModel,
|
entity:
|
||||||
|
| SearchedDataProps['data'][number]['_source']
|
||||||
|
| DashboardDataModel
|
||||||
|
| Database,
|
||||||
entityType?: EntityType,
|
entityType?: EntityType,
|
||||||
includeCurrent = false
|
includeCurrent = false
|
||||||
) => {
|
) => {
|
||||||
@ -1029,6 +1035,18 @@ export const getEntityBreadcrumbs = (
|
|||||||
})),
|
})),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
case EntityType.DATABASE:
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
name: startCase(ServiceCategory.DATABASE_SERVICES),
|
||||||
|
url: getSettingPath(
|
||||||
|
GlobalSettingsMenuCategory.SERVICES,
|
||||||
|
getServiceRouteFromServiceType(ServiceCategory.DATABASE_SERVICES)
|
||||||
|
),
|
||||||
|
},
|
||||||
|
...getBreadcrumbForEntitiesWithServiceOnly(entity as Database),
|
||||||
|
];
|
||||||
|
|
||||||
case EntityType.TOPIC:
|
case EntityType.TOPIC:
|
||||||
case EntityType.DASHBOARD:
|
case EntityType.DASHBOARD:
|
||||||
case EntityType.PIPELINE:
|
case EntityType.PIPELINE:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user