mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-10-14 10:18:23 +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 { Dashboard } from 'generated/entity/data/dashboard';
|
||||
import { DashboardDataModel } from 'generated/entity/data/dashboardDataModel';
|
||||
import { Database } from 'generated/entity/data/database';
|
||||
import { Mlmodel } from 'generated/entity/data/mlmodel';
|
||||
import { Pipeline } from 'generated/entity/data/pipeline';
|
||||
import { Table } from 'generated/entity/data/table';
|
||||
@ -56,6 +57,7 @@ import { getCurrentUserId, getEntityDetailLink } from 'utils/CommonUtils';
|
||||
import {
|
||||
getBreadcrumbForEntitiesWithServiceOnly,
|
||||
getBreadcrumbForTable,
|
||||
getEntityBreadcrumbs,
|
||||
getEntityFeedLink,
|
||||
getEntityName,
|
||||
} from 'utils/EntityUtils';
|
||||
@ -66,6 +68,7 @@ import { showErrorToast } from 'utils/ToastUtils';
|
||||
import {
|
||||
DataAssetHeaderInfo,
|
||||
DataAssetsHeaderProps,
|
||||
DataAssetType,
|
||||
} from './DataAssetsHeader.interface';
|
||||
|
||||
export const ExtraInfoLabel = ({
|
||||
@ -108,6 +111,7 @@ export const ExtraInfoLink = ({
|
||||
);
|
||||
|
||||
export const DataAssetsHeader = ({
|
||||
allowSoftDelete = true,
|
||||
dataAsset,
|
||||
onOwnerUpdate,
|
||||
onTierUpdate,
|
||||
@ -115,6 +119,7 @@ export const DataAssetsHeader = ({
|
||||
onVersionClick,
|
||||
onFollowClick,
|
||||
entityType,
|
||||
isRecursiveDelete,
|
||||
onRestoreDataAsset,
|
||||
onDisplayNameUpdate,
|
||||
}: DataAssetsHeaderProps) => {
|
||||
@ -133,13 +138,23 @@ export const DataAssetsHeader = ({
|
||||
);
|
||||
const [copyTooltip, setCopyTooltip] = useState<string>();
|
||||
|
||||
const excludeEntityService = [EntityType.DATABASE].includes(entityType);
|
||||
const hasFollowers = 'followers' in dataAsset;
|
||||
|
||||
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 ?? []),
|
||||
entityName: getEntityName(dataAsset),
|
||||
version: dataAsset.version,
|
||||
followers: dataAsset.followers?.length,
|
||||
}),
|
||||
[dataAsset, USERId]
|
||||
);
|
||||
@ -363,6 +378,16 @@ export const DataAssetsHeader = ({
|
||||
|
||||
break;
|
||||
|
||||
case EntityType.DATABASE:
|
||||
const databaseDetails = dataAsset as Database;
|
||||
|
||||
returnData.breadcrumbs = getEntityBreadcrumbs(
|
||||
databaseDetails,
|
||||
EntityType.DATABASE
|
||||
);
|
||||
|
||||
break;
|
||||
|
||||
case EntityType.TABLE:
|
||||
default:
|
||||
const tableDetails = dataAsset as Table;
|
||||
@ -492,27 +517,36 @@ export const DataAssetsHeader = ({
|
||||
<Space className="items-end w-full" direction="vertical" size={16}>
|
||||
<Space>
|
||||
<ButtonGroup size="small">
|
||||
<Button
|
||||
className="w-16 p-0"
|
||||
icon={<Icon component={TaskOpenIcon} />}
|
||||
onClick={handleOpenTaskClick}>
|
||||
<Typography.Text>{taskCount}</Typography.Text>
|
||||
</Button>
|
||||
<Button
|
||||
className="w-16 p-0"
|
||||
icon={<Icon component={VersionIcon} />}
|
||||
onClick={onVersionClick}>
|
||||
<Typography.Text>{version}</Typography.Text>
|
||||
</Button>
|
||||
<Button
|
||||
className="w-16 p-0"
|
||||
data-testid="entity-follow-button"
|
||||
icon={
|
||||
<Icon component={isFollowing ? StarFilledIcon : StarIcon} />
|
||||
}
|
||||
onClick={onFollowClick}>
|
||||
<Typography.Text>{followers}</Typography.Text>
|
||||
</Button>
|
||||
{!excludeEntityService && (
|
||||
<>
|
||||
<Button
|
||||
className="w-16 p-0"
|
||||
icon={<Icon component={TaskOpenIcon} />}
|
||||
onClick={handleOpenTaskClick}>
|
||||
<Typography.Text>{taskCount}</Typography.Text>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
className="w-16 p-0"
|
||||
icon={<Icon component={VersionIcon} />}
|
||||
onClick={onVersionClick}>
|
||||
<Typography.Text>{version}</Typography.Text>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
className="w-16 p-0"
|
||||
data-testid="entity-follow-button"
|
||||
icon={
|
||||
<Icon
|
||||
component={isFollowing ? StarFilledIcon : StarIcon}
|
||||
/>
|
||||
}
|
||||
onClick={onFollowClick}>
|
||||
<Typography.Text>{followers}</Typography.Text>
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Tooltip
|
||||
open={!isEmpty(copyTooltip)}
|
||||
placement="bottomRight"
|
||||
@ -523,7 +557,7 @@ export const DataAssetsHeader = ({
|
||||
/>
|
||||
</Tooltip>
|
||||
<ManageButton
|
||||
allowSoftDelete={!dataAsset.deleted}
|
||||
allowSoftDelete={!dataAsset.deleted && allowSoftDelete}
|
||||
canDelete={permissions.Delete}
|
||||
deleted={dataAsset.deleted}
|
||||
displayName={dataAsset.displayName}
|
||||
@ -534,6 +568,7 @@ export const DataAssetsHeader = ({
|
||||
entityId={dataAsset.id}
|
||||
entityName={entityName}
|
||||
entityType={entityType}
|
||||
isRecursiveDelete={isRecursiveDelete}
|
||||
onAnnouncementClick={
|
||||
permissions?.EditAll
|
||||
? () => setIsAnnouncementDrawer(true)
|
||||
|
@ -17,6 +17,7 @@ import { EntityType } from 'enums/entity.enum';
|
||||
import { Container } from 'generated/entity/data/container';
|
||||
import { Dashboard } from 'generated/entity/data/dashboard';
|
||||
import { DashboardDataModel } from 'generated/entity/data/dashboardDataModel';
|
||||
import { Database } from 'generated/entity/data/database';
|
||||
import { Mlmodel } from 'generated/entity/data/mlmodel';
|
||||
import { Pipeline } from 'generated/entity/data/pipeline';
|
||||
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 { ReactNode } from 'react';
|
||||
|
||||
export type DataAssetsSourceMapping = {
|
||||
[EntityType.TABLE]: Table;
|
||||
[EntityType.TOPIC]: Topic;
|
||||
[EntityType.DASHBOARD]: Dashboard;
|
||||
[EntityType.PIPELINE]: Pipeline;
|
||||
[EntityType.MLMODEL]: Mlmodel;
|
||||
[EntityType.CONTAINER]: Container;
|
||||
};
|
||||
export type DataAssetType =
|
||||
| Table
|
||||
| Topic
|
||||
| Dashboard
|
||||
| Pipeline
|
||||
| Mlmodel
|
||||
| Container
|
||||
| DashboardDataModel;
|
||||
|
||||
export type DataAssetsHeaderProps = {
|
||||
permissions: OperationPermission;
|
||||
allowSoftDelete?: boolean;
|
||||
isRecursiveDelete?: boolean;
|
||||
onTierUpdate: (tier?: string) => Promise<void>;
|
||||
onOwnerUpdate: (owner?: EntityReference) => Promise<void>;
|
||||
onVersionClick: () => void;
|
||||
onFollowClick: () => Promise<void>;
|
||||
onVersionClick?: () => void;
|
||||
onFollowClick?: () => Promise<void>;
|
||||
onRestoreDataAsset: () => Promise<void>;
|
||||
onDisplayNameUpdate: (data: EntityName) => Promise<void>;
|
||||
} & (
|
||||
@ -49,6 +52,7 @@ export type DataAssetsHeaderProps = {
|
||||
| DataAssetMlmodel
|
||||
| DataAssetContainer
|
||||
| DataAssetDashboardDataModel
|
||||
| DataAssetDatabase
|
||||
);
|
||||
|
||||
export interface DataAssetTable {
|
||||
@ -85,6 +89,11 @@ export interface DataAssetDashboardDataModel {
|
||||
entityType: EntityType.DASHBOARD_DATA_MODEL;
|
||||
}
|
||||
|
||||
export interface DataAssetDatabase {
|
||||
dataAsset: Database;
|
||||
entityType: EntityType.DATABASE;
|
||||
}
|
||||
|
||||
export interface DataAssetHeaderInfo {
|
||||
extraInfo: ReactNode;
|
||||
breadcrumbs: TitleBreadcrumbProps['titleLinks'];
|
||||
|
@ -128,7 +128,8 @@ export type EntityWithServices =
|
||||
| Pipeline
|
||||
| Mlmodel
|
||||
| Container
|
||||
| DashboardDataModel;
|
||||
| DashboardDataModel
|
||||
| Database;
|
||||
|
||||
export interface EntityDetailsObjectInterface {
|
||||
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 = {
|
||||
data: [
|
||||
{
|
||||
@ -100,59 +90,6 @@ const mockSchemaData = {
|
||||
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 = {
|
||||
totalCount: 6,
|
||||
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', () => {
|
||||
return jest.fn().mockReturnValue({
|
||||
inPageSearchText: '',
|
||||
@ -229,60 +179,24 @@ jest.mock('rest/databaseAPI', () => ({
|
||||
}));
|
||||
|
||||
jest.mock('rest/feedsAPI', () => ({
|
||||
getAllFeeds: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve(mockAllFeeds)),
|
||||
getFeedCount: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve(mockFeedCount)),
|
||||
|
||||
postFeedById: 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', () => ({
|
||||
getOwnerFromId: jest.fn().mockReturnValue({
|
||||
name: 'owner',
|
||||
id: 'string',
|
||||
type: 'user',
|
||||
}),
|
||||
getUsagePercentile: jest.fn().mockReturnValue('Medium - 45th pctile'),
|
||||
getTierTags: 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', () => {
|
||||
return jest.fn().mockReturnValue(<div>NextPrevious</div>);
|
||||
});
|
||||
|
||||
jest.mock(
|
||||
'components/common/title-breadcrumb/title-breadcrumb.component',
|
||||
() => {
|
||||
return jest.fn().mockReturnValue(<div>TitleBreadcrumb</div>);
|
||||
}
|
||||
);
|
||||
|
||||
jest.mock('components/FeedEditor/FeedEditor', () => {
|
||||
return jest.fn().mockReturnValue(<p>FeedEditor</p>);
|
||||
jest.mock('components/Tag/TagsContainerV2/TagsContainerV2', () => {
|
||||
return jest.fn().mockReturnValue(<div>TagsContainerV2</div>);
|
||||
});
|
||||
|
||||
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>);
|
||||
});
|
||||
|
||||
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', () => {
|
||||
return jest.fn().mockImplementation(({ children }) => children);
|
||||
});
|
||||
|
||||
jest.mock('components/Entity/EntityHeader/EntityHeader.component', () => ({
|
||||
EntityHeader: jest.fn().mockImplementation(() => <p>EntityHeader</p>),
|
||||
}));
|
||||
jest.mock(
|
||||
'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', () => {
|
||||
it('Component should render', async () => {
|
||||
@ -347,11 +260,8 @@ describe('Test DatabaseDetails page', () => {
|
||||
wrapper: MemoryRouter,
|
||||
});
|
||||
|
||||
const entityHeader = await findByText(container, 'EntityHeader');
|
||||
const descriptionContainer = await findByTestId(
|
||||
container,
|
||||
'description-container'
|
||||
);
|
||||
const entityHeader = await findByText(container, 'DataAssetsHeader');
|
||||
const descriptionContainer = await findByText(container, 'Description');
|
||||
const databaseTable = await findByTestId(
|
||||
container,
|
||||
'database-databaseSchemas'
|
||||
@ -421,11 +331,8 @@ describe('Test DatabaseDetails page', () => {
|
||||
wrapper: MemoryRouter,
|
||||
});
|
||||
|
||||
const entityHeader = await findByText(container, 'EntityHeader');
|
||||
const descriptionContainer = await findByTestId(
|
||||
container,
|
||||
'description-container'
|
||||
);
|
||||
const entityHeader = await findByText(container, 'DataAssetsHeader');
|
||||
const descriptionContainer = await findByText(container, 'Description');
|
||||
const databaseTable = await findByTestId(
|
||||
container,
|
||||
'database-databaseSchemas'
|
||||
|
@ -11,16 +11,7 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
Col,
|
||||
Row,
|
||||
Skeleton,
|
||||
Space,
|
||||
Switch,
|
||||
Table,
|
||||
Tabs,
|
||||
Typography,
|
||||
} from 'antd';
|
||||
import { Col, Row, Space, Switch, Table, Tabs, Typography } from 'antd';
|
||||
import { ColumnsType } from 'antd/lib/table';
|
||||
import { AxiosError } from 'axios';
|
||||
import ActivityFeedProvider, {
|
||||
@ -29,14 +20,11 @@ import ActivityFeedProvider, {
|
||||
import { ActivityFeedTab } from 'components/ActivityFeed/ActivityFeedTab/ActivityFeedTab.component';
|
||||
import ActivityThreadPanel from 'components/ActivityFeed/ActivityThreadPanel/ActivityThreadPanel';
|
||||
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 NextPrevious from 'components/common/next-previous/NextPrevious';
|
||||
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 { EntityHeader } from 'components/Entity/EntityHeader/EntityHeader.component';
|
||||
import { DataAssetsHeader } from 'components/DataAssets/DataAssetsHeader/DataAssetsHeader.component';
|
||||
import Loader from 'components/Loader/Loader';
|
||||
import { EntityName } from 'components/Modals/EntityNameModal/EntityNameModal.interface';
|
||||
import { usePermissionProvider } from 'components/PermissionProvider/PermissionProvider';
|
||||
@ -45,15 +33,15 @@ import {
|
||||
ResourceEntity,
|
||||
} from 'components/PermissionProvider/PermissionProvider.interface';
|
||||
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 { compare, Operation } from 'fast-json-patch';
|
||||
import { LabelType } from 'generated/entity/data/table';
|
||||
import { Include } from 'generated/type/include';
|
||||
import { State } from 'generated/type/tagLabel';
|
||||
import { isEmpty, isNil, isUndefined, startCase } from 'lodash';
|
||||
import { State, TagSource } from 'generated/type/tagLabel';
|
||||
import { isEmpty, isNil, isUndefined } from 'lodash';
|
||||
import { observer } from 'mobx-react';
|
||||
import { EntityTags, ExtraInfo, TagOption } from 'Models';
|
||||
import { EntityTags } from 'Models';
|
||||
import React, {
|
||||
FunctionComponent,
|
||||
useCallback,
|
||||
@ -70,23 +58,17 @@ import {
|
||||
patchDatabaseDetails,
|
||||
} from 'rest/databaseAPI';
|
||||
import { getFeedCount, postThread } from 'rest/feedsAPI';
|
||||
import { fetchTagsAndGlossaryTerms } from 'utils/TagsUtils';
|
||||
import { default as appState } from '../../AppState';
|
||||
import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants';
|
||||
import {
|
||||
getDatabaseDetailsPath,
|
||||
getDatabaseSchemaDetailsPath,
|
||||
getExplorePath,
|
||||
getServiceDetailsPath,
|
||||
getTeamAndUserDetailsPath,
|
||||
PAGE_SIZE,
|
||||
pagingObject,
|
||||
} from '../../constants/constants';
|
||||
import { EntityField } from '../../constants/Feeds.constants';
|
||||
import { GlobalSettingsMenuCategory } from '../../constants/GlobalSettings.constants';
|
||||
import { EntityInfo, EntityTabs, EntityType } from '../../enums/entity.enum';
|
||||
import { ServiceCategory } from '../../enums/service.enum';
|
||||
import { OwnerType } from '../../enums/user.enum';
|
||||
import { EntityTabs, EntityType } from '../../enums/entity.enum';
|
||||
import { CreateThread } from '../../generated/api/feed/createThread';
|
||||
import { Database } from '../../generated/entity/data/database';
|
||||
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 { Paging } from '../../generated/type/paging';
|
||||
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 { DEFAULT_ENTITY_PERMISSION } from '../../utils/PermissionsUtils';
|
||||
import { getSettingPath } from '../../utils/RouterUtils';
|
||||
import {
|
||||
getServiceRouteFromServiceType,
|
||||
serviceTypeLogo,
|
||||
} from '../../utils/ServiceUtils';
|
||||
import { getErrorText } from '../../utils/StringsUtils';
|
||||
import {
|
||||
getTagsWithoutTier,
|
||||
@ -113,16 +94,13 @@ import { showErrorToast } from '../../utils/ToastUtils';
|
||||
const DatabaseDetails: FunctionComponent = () => {
|
||||
const { t } = useTranslation();
|
||||
const { postFeed, deleteFeed, updateFeed } = useActivityFeedProvider();
|
||||
const [slashedDatabaseName, setSlashedDatabaseName] = useState<
|
||||
TitleBreadcrumbProps['titleLinks']
|
||||
>([]);
|
||||
const { getEntityPermissionByFqn } = usePermissionProvider();
|
||||
|
||||
const { databaseFQN, tab: activeTab = EntityTabs.SCHEMA } =
|
||||
useParams<{ databaseFQN: string; tab: EntityTabs }>();
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [showDeletedSchemas, setShowDeletedSchemas] = useState<boolean>(false);
|
||||
const [database, setDatabase] = useState<Database>();
|
||||
const [database, setDatabase] = useState<Database>({} as Database);
|
||||
const [serviceType, setServiceType] = useState<string>();
|
||||
const [schemaData, setSchemaData] = useState<DatabaseSchema[]>([]);
|
||||
const [schemaDataLoading, setSchemaDataLoading] = useState<boolean>(true);
|
||||
@ -148,9 +126,6 @@ const DatabaseDetails: FunctionComponent = () => {
|
||||
|
||||
const [threadLink, setThreadLink] = useState<string>('');
|
||||
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 isMounting = useRef(true);
|
||||
@ -158,8 +133,6 @@ const DatabaseDetails: FunctionComponent = () => {
|
||||
const tier = getTierTags(database?.tags ?? []);
|
||||
const tags = getTagsWithoutTier(database?.tags ?? []);
|
||||
|
||||
const deleted = database?.deleted;
|
||||
|
||||
const [databasePermission, setDatabasePermission] =
|
||||
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) => {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
setSchemaDataLoading(true);
|
||||
@ -302,34 +224,12 @@ const DatabaseDetails: FunctionComponent = () => {
|
||||
getDatabaseDetailsByFQN(databaseFQN, ['owner', 'tags'])
|
||||
.then((res) => {
|
||||
if (res) {
|
||||
const { description, id, name, service, serviceType } = res;
|
||||
const { description, id, name, serviceType } = res;
|
||||
setDatabase(res);
|
||||
setDescription(description ?? '');
|
||||
setDatabaseId(id ?? '');
|
||||
setDatabaseName(name);
|
||||
|
||||
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();
|
||||
} else {
|
||||
throw t('server.unexpected-response');
|
||||
@ -431,13 +331,13 @@ const DatabaseDetails: FunctionComponent = () => {
|
||||
};
|
||||
|
||||
const handleUpdateOwner = useCallback(
|
||||
(owner: Database['owner']) => {
|
||||
async (owner: Database['owner']) => {
|
||||
const updatedData = {
|
||||
...database,
|
||||
owner: owner ? { ...database?.owner, ...owner } : undefined,
|
||||
};
|
||||
|
||||
settingsUpdateHandler(updatedData as Database);
|
||||
await settingsUpdateHandler(updatedData as Database);
|
||||
},
|
||||
[database, database?.owner, settingsUpdateHandler]
|
||||
);
|
||||
@ -579,51 +479,19 @@ const DatabaseDetails: FunctionComponent = () => {
|
||||
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
|
||||
* @param selectedTags
|
||||
*/
|
||||
const onTagUpdate = (selectedTags?: Array<EntityTags>) => {
|
||||
const onTagUpdate = async (selectedTags?: Array<EntityTags>) => {
|
||||
if (selectedTags) {
|
||||
const updatedTags = [...(tier ? [tier] : []), ...selectedTags];
|
||||
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) {
|
||||
const prevTags =
|
||||
tags?.filter((tag) =>
|
||||
@ -643,9 +511,8 @@ const DatabaseDetails: FunctionComponent = () => {
|
||||
source: tag.source,
|
||||
tagFQN: tag.tagFQN,
|
||||
}));
|
||||
onTagUpdate([...prevTags, ...newTags]);
|
||||
await onTagUpdate([...prevTags, ...newTags]);
|
||||
}
|
||||
setIsEditable(false);
|
||||
};
|
||||
|
||||
const databaseTable = useMemo(() => {
|
||||
@ -659,10 +526,6 @@ const DatabaseDetails: FunctionComponent = () => {
|
||||
columns={tableColumn}
|
||||
data-testid="database-databaseSchemas"
|
||||
dataSource={schemaData}
|
||||
loading={{
|
||||
spinning: schemaDataLoading,
|
||||
indicator: <Loader size="small" />,
|
||||
}}
|
||||
pagination={false}
|
||||
rowKey="id"
|
||||
size="small"
|
||||
@ -693,11 +556,136 @@ const DatabaseDetails: FunctionComponent = () => {
|
||||
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(() => {
|
||||
fetchDatabaseSchemas();
|
||||
}, [showDeletedSchemas]);
|
||||
|
||||
if (isLoading) {
|
||||
if (isLoading || isDatabaseDetailsLoading) {
|
||||
return <Loader />;
|
||||
}
|
||||
|
||||
@ -709,217 +697,53 @@ const DatabaseDetails: FunctionComponent = () => {
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{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>
|
||||
)}
|
||||
if (!(databasePermission.ViewAll || databasePermission.ViewBasic)) {
|
||||
return <ErrorPlaceHolder type={ERROR_PLACEHOLDER_TYPE.PERMISSION} />;
|
||||
}
|
||||
|
||||
<Col span={24}>
|
||||
<Row className="m-t-md">
|
||||
<Col span={24}>
|
||||
<Tabs
|
||||
activeKey={activeTab ?? EntityTabs.SCHEMA}
|
||||
items={tabs}
|
||||
onChange={activeTabHandler}
|
||||
/>
|
||||
</Col>
|
||||
<Col className="p-y-md" span={24}>
|
||||
{activeTab === EntityTabs.SCHEMA && (
|
||||
<>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col data-testid="description-container" span={24}>
|
||||
<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}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<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>
|
||||
</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} />
|
||||
)}
|
||||
</>
|
||||
return (
|
||||
<PageLayoutV1
|
||||
className="bg-white"
|
||||
pageTitle={t('label.entity-detail-plural', {
|
||||
entity: getEntityName(database),
|
||||
})}>
|
||||
<Row gutter={[0, 12]}>
|
||||
<Col className="p-x-lg" span={24}>
|
||||
<DataAssetsHeader
|
||||
isRecursiveDelete
|
||||
allowSoftDelete={false}
|
||||
dataAsset={database}
|
||||
entityType={EntityType.DATABASE}
|
||||
permissions={databasePermission}
|
||||
onDisplayNameUpdate={handleUpdateDisplayName}
|
||||
onOwnerUpdate={handleUpdateOwner}
|
||||
onRestoreDataAsset={() => Promise.resolve()}
|
||||
onTierUpdate={handleUpdateTier}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Tabs
|
||||
activeKey={activeTab ?? EntityTabs.SCHEMA}
|
||||
className="entity-details-page-tabs"
|
||||
data-testid="tabs"
|
||||
items={tabs}
|
||||
onChange={activeTabHandler}
|
||||
/>
|
||||
</Col>
|
||||
|
||||
{threadLink ? (
|
||||
<ActivityThreadPanel
|
||||
createThread={createThread}
|
||||
deletePostHandler={deleteFeed}
|
||||
open={Boolean(threadLink)}
|
||||
postFeedHandler={postFeed}
|
||||
threadLink={threadLink}
|
||||
updateThreadHandler={updateFeed}
|
||||
onCancel={onThreadPanelClose}
|
||||
/>
|
||||
) : null}
|
||||
</Row>
|
||||
</PageLayoutV1>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -28,10 +28,12 @@ import {
|
||||
SourceType,
|
||||
} from 'components/searched-data/SearchedData.interface';
|
||||
import { EntityField } from 'constants/Feeds.constants';
|
||||
import { GlobalSettingsMenuCategory } from 'constants/GlobalSettings.constants';
|
||||
import { ExplorePageTabs } from 'enums/Explore.enum';
|
||||
import { Tag } from 'generated/entity/classification/tag';
|
||||
import { Container } from 'generated/entity/data/container';
|
||||
import { DashboardDataModel } from 'generated/entity/data/dashboardDataModel';
|
||||
import { Database } from 'generated/entity/data/database';
|
||||
import { GlossaryTerm } from 'generated/entity/data/glossaryTerm';
|
||||
import { Mlmodel } from 'generated/entity/data/mlmodel';
|
||||
import { Topic } from 'generated/entity/data/topic';
|
||||
@ -79,7 +81,8 @@ import {
|
||||
} from './CommonUtils';
|
||||
import { getEntityFieldThreadCounts } from './FeedUtils';
|
||||
import Fqn from './Fqn';
|
||||
import { getGlossaryPath } from './RouterUtils';
|
||||
import { getGlossaryPath, getSettingPath } from './RouterUtils';
|
||||
import { getServiceRouteFromServiceType } from './ServiceUtils';
|
||||
import {
|
||||
getDataTypeString,
|
||||
getTierFromTableTags,
|
||||
@ -985,7 +988,10 @@ export const getBreadcrumbForEntitiesWithServiceOnly = (
|
||||
};
|
||||
|
||||
export const getEntityBreadcrumbs = (
|
||||
entity: SearchedDataProps['data'][number]['_source'] | DashboardDataModel,
|
||||
entity:
|
||||
| SearchedDataProps['data'][number]['_source']
|
||||
| DashboardDataModel
|
||||
| Database,
|
||||
entityType?: EntityType,
|
||||
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.DASHBOARD:
|
||||
case EntityType.PIPELINE:
|
||||
|
Loading…
x
Reference in New Issue
Block a user