From f8ff645e5d65c98ff9df2f75963ab7c0efa25e82 Mon Sep 17 00:00:00 2001 From: Ashish Gupta Date: Thu, 6 Jul 2023 12:29:45 +0530 Subject: [PATCH] feat(ui): supported latest ui for the database page (#12277) * supported latest ui for the database page * fix unit test --- .../DataAssetsHeader.component.tsx | 83 ++- .../DataAssetsHeader.interface.ts | 29 +- .../components/Explore/explore.interface.ts | 3 +- .../src/pages/database-details/index.test.tsx | 177 ++---- .../ui/src/pages/database-details/index.tsx | 560 ++++++------------ .../resources/ui/src/utils/EntityUtils.tsx | 22 +- 6 files changed, 334 insertions(+), 540 deletions(-) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetsHeader/DataAssetsHeader.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetsHeader/DataAssetsHeader.component.tsx index a15a63d6434..23c7a7a8f8b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetsHeader/DataAssetsHeader.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetsHeader/DataAssetsHeader.component.tsx @@ -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(); + 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 = ({ - - - + {!excludeEntityService && ( + <> + + + + + + + )} + setIsAnnouncementDrawer(true) diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetsHeader/DataAssetsHeader.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetsHeader/DataAssetsHeader.interface.ts index 5596e16b121..4704b743a10 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetsHeader/DataAssetsHeader.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/DataAssets/DataAssetsHeader/DataAssetsHeader.interface.ts @@ -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; onOwnerUpdate: (owner?: EntityReference) => Promise; - onVersionClick: () => void; - onFollowClick: () => Promise; + onVersionClick?: () => void; + onFollowClick?: () => Promise; onRestoreDataAsset: () => Promise; onDisplayNameUpdate: (data: EntityName) => Promise; } & ( @@ -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']; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Explore/explore.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/Explore/explore.interface.ts index 59c0c1a16cd..58478f771ac 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Explore/explore.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/explore.interface.ts @@ -128,7 +128,8 @@ export type EntityWithServices = | Pipeline | Mlmodel | Container - | DashboardDataModel; + | DashboardDataModel + | Database; export interface EntityDetailsObjectInterface { details: SearchedDataProps['data'][number]['_source']; diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/database-details/index.test.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/database-details/index.test.tsx index 770ccc5eb2c..01cfafd1f3f 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/database-details/index.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/database-details/index.test.tsx @@ -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(Tag); -}); - jest.mock('components/common/next-previous/NextPrevious', () => { return jest.fn().mockReturnValue(
NextPrevious
); }); -jest.mock( - 'components/common/title-breadcrumb/title-breadcrumb.component', - () => { - return jest.fn().mockReturnValue(
TitleBreadcrumb
); - } -); - -jest.mock('components/FeedEditor/FeedEditor', () => { - return jest.fn().mockReturnValue(

FeedEditor

); +jest.mock('components/Tag/TagsContainerV2/TagsContainerV2', () => { + return jest.fn().mockReturnValue(
TagsContainerV2
); }); 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(

Description

); }); -jest.mock('components/common/EntitySummaryDetails/EntitySummaryDetails', () => { - return jest - .fn() - .mockReturnValue( -

EntitySummaryDetails component

- ); -}); - -jest.mock('components/common/DeleteWidget/DeleteWidgetModal', () => { - return jest - .fn() - .mockReturnValue( -

DeleteWidgetModal component

- ); -}); - -jest.mock('components/MyData/LeftSidebar/LeftSidebar.component', () => - jest.fn().mockReturnValue(

Sidebar

) -); - jest.mock('components/containers/PageLayoutV1', () => { return jest.fn().mockImplementation(({ children }) => children); }); -jest.mock('components/Entity/EntityHeader/EntityHeader.component', () => ({ - EntityHeader: jest.fn().mockImplementation(() =>

EntityHeader

), -})); +jest.mock( + 'components/ActivityFeed/ActivityFeedTab/ActivityFeedTab.component', + () => { + return jest.fn().mockReturnValue(

ActivityFeedTab

); + } +); + +jest.mock( + 'components/ActivityFeed/ActivityThreadPanel/ActivityThreadPanel', + () => { + return jest.fn().mockReturnValue(

ActivityThreadPanel

); + } +); + +jest.mock( + 'components/DataAssets/DataAssetsHeader/DataAssetsHeader.component', + () => ({ + DataAssetsHeader: jest + .fn() + .mockImplementation(() =>

DataAssetsHeader

), + }) +); 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' diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/database-details/index.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/database-details/index.tsx index a6d7f713d9a..b6194c5b800 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/database-details/index.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/database-details/index.tsx @@ -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(false); - const [database, setDatabase] = useState(); + const [database, setDatabase] = useState({} as Database); const [serviceType, setServiceType] = useState(); const [schemaData, setSchemaData] = useState([]); const [schemaDataLoading, setSchemaDataLoading] = useState(true); @@ -148,9 +126,6 @@ const DatabaseDetails: FunctionComponent = () => { const [threadLink, setThreadLink] = useState(''); const [currentPage, setCurrentPage] = useState(1); - const [isEditable, setIsEditable] = useState(false); - const [tagList, setTagList] = useState>([]); - const [isTagLoading, setIsTagLoading] = useState(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(DEFAULT_ENTITY_PERMISSION); @@ -178,57 +151,6 @@ const DatabaseDetails: FunctionComponent = () => { } }; - const tabs = useMemo(() => { - return [ - { - label: ( - - ), - key: EntityTabs.SCHEMA, - }, - { - label: ( - - ), - key: EntityTabs.ACTIVITY_FEED, - }, - ]; - }, [activeTab, databaseSchemaInstanceCount, feedCount]); - - const extraInfo: Array = [ - { - 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((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) => { + const onTagUpdate = async (selectedTags?: Array) => { 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) => { + 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: , - }} pagination={false} rowKey="id" size="small" @@ -693,11 +556,136 @@ const DatabaseDetails: FunctionComponent = () => { databaseSchemaPagingHandler, ]); + const tabs = useMemo( + () => [ + { + label: ( + + ), + key: EntityTabs.SCHEMA, + children: ( + + +
+ + + + + + {t('label.deleted')} + {' '} + + + + {databaseTable} +
+ + + + + + + +
+ ), + }, + { + label: ( + + ), + key: EntityTabs.ACTIVITY_FEED, + children: ( + + + + ), + }, + ], + [ + tags, + isEdit, + database, + description, + databaseName, + entityFieldThreadCount, + databaseFQN, + activeTab, + databaseTable, + databasePermission, + databaseSchemaInstanceCount, + feedCount, + showDeletedSchemas, + ] + ); + useEffect(() => { fetchDatabaseSchemas(); }, [showDeletedSchemas]); - if (isLoading) { + if (isLoading || isDatabaseDetailsLoading) { return ; } @@ -709,217 +697,53 @@ const DatabaseDetails: FunctionComponent = () => { ); } - return ( - <> - {databasePermission.ViewAll || databasePermission.ViewBasic ? ( - - - {isDatabaseDetailsLoading ? ( - - ) : ( - - {database && ( - - - - } - serviceName={database.service.name ?? ''} - /> - - - - - - )} - - - {extraInfo.map((info, index) => ( - - - {extraInfo.length !== 1 && - index < extraInfo.length - 1 ? ( - - {t('label.pipe-symbol')} - - ) : null} - - ))} - - - - { - if (isTagEditable) { - // Fetch tags and terms only once - if (tagList.length === 0) { - fetchTags(); - } - setIsEditable(true); - } - }}> - {!deleted && ( - { - handleTagSelection(); - }} - onSelectionChange={(tags) => { - handleTagSelection(tags); - }} - /> - )} - - - - )} + if (!(databasePermission.ViewAll || databasePermission.ViewBasic)) { + return ; + } - - - - - - - {activeTab === EntityTabs.SCHEMA && ( - <> - - - - - - - - - - {t('label.deleted')} - {' '} - - - - {databaseTable} - - - )} - {activeTab === EntityTabs.ACTIVITY_FEED && ( - - - - )} - - - - - {threadLink ? ( - - ) : null} - - - - ) : ( - - )} - + return ( + + + + Promise.resolve()} + onTierUpdate={handleUpdateTier} + /> + + + + + + {threadLink ? ( + + ) : null} + + ); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/EntityUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/EntityUtils.tsx index 255a619b0fc..cc2ac4197ef 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/EntityUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/EntityUtils.tsx @@ -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: