diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Database/DatabaseSchema/DatabaseSchemaTable/DatabaseSchemaTable.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/Database/DatabaseSchema/DatabaseSchemaTable/DatabaseSchemaTable.interface.ts index 792196cb0c30..9038eef2b3c8 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Database/DatabaseSchema/DatabaseSchemaTable/DatabaseSchemaTable.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/Database/DatabaseSchema/DatabaseSchemaTable/DatabaseSchemaTable.interface.ts @@ -13,4 +13,5 @@ export interface DatabaseSchemaTableProps { isDatabaseDeleted?: boolean; + isVersionPage?: boolean; } diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Database/DatabaseSchema/DatabaseSchemaTable/DatabaseSchemaTable.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Database/DatabaseSchema/DatabaseSchemaTable/DatabaseSchemaTable.tsx index 36acb772229d..64148e9108e6 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Database/DatabaseSchema/DatabaseSchemaTable/DatabaseSchemaTable.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Database/DatabaseSchema/DatabaseSchemaTable/DatabaseSchemaTable.tsx @@ -11,45 +11,70 @@ * limitations under the License. */ import { Col, Row, Switch, Typography } from 'antd'; +import { ColumnsType } from 'antd/lib/table'; import { AxiosError } from 'axios'; +import { compare } from 'fast-json-patch'; import { t } from 'i18next'; import { isEmpty } from 'lodash'; import QueryString from 'qs'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useHistory } from 'react-router-dom'; import { + getEntityDetailsPath, INITIAL_PAGING_VALUE, + NO_DATA_PLACEHOLDER, PAGE_SIZE, } from '../../../../constants/constants'; -import { TabSpecificField } from '../../../../enums/entity.enum'; +import { usePermissionProvider } from '../../../../context/PermissionProvider/PermissionProvider'; +import { EntityType, TabSpecificField } from '../../../../enums/entity.enum'; import { SearchIndex } from '../../../../enums/search.enum'; import { DatabaseSchema } from '../../../../generated/entity/data/databaseSchema'; +import { EntityReference } from '../../../../generated/entity/type'; +import { UsageDetails } from '../../../../generated/type/entityUsage'; import { Include } from '../../../../generated/type/include'; import { Paging } from '../../../../generated/type/paging'; import { usePaging } from '../../../../hooks/paging/usePaging'; import useCustomLocation from '../../../../hooks/useCustomLocation/useCustomLocation'; import { useFqn } from '../../../../hooks/useFqn'; -import { getDatabaseSchemas } from '../../../../rest/databaseAPI'; +import { + getDatabaseSchemas, + patchDatabaseSchemaDetails, +} from '../../../../rest/databaseAPI'; import { searchQuery } from '../../../../rest/searchAPI'; -import { schemaTableColumns } from '../../../../utils/Database/Database.util'; +import { getEntityName } from '../../../../utils/EntityUtils'; +import { getUsagePercentile } from '../../../../utils/TableUtils'; import { showErrorToast } from '../../../../utils/ToastUtils'; +import DisplayName from '../../../common/DisplayName/DisplayName'; import ErrorPlaceHolder from '../../../common/ErrorWithPlaceholder/ErrorPlaceHolder'; import NextPrevious from '../../../common/NextPrevious/NextPrevious'; import { PagingHandlerParams } from '../../../common/NextPrevious/NextPrevious.interface'; +import RichTextEditorPreviewer from '../../../common/RichTextEditor/RichTextEditorPreviewer'; import Searchbar from '../../../common/SearchBarComponent/SearchBar.component'; import Table from '../../../common/Table/Table'; +import { EntityName } from '../../../Modals/EntityNameModal/EntityNameModal.interface'; import { DatabaseSchemaTableProps } from './DatabaseSchemaTable.interface'; export const DatabaseSchemaTable = ({ isDatabaseDeleted, + isVersionPage = false, }: Readonly) => { const { fqn: decodedDatabaseFQN } = useFqn(); const history = useHistory(); const location = useCustomLocation(); + const { permissions } = usePermissionProvider(); + const [schemas, setSchemas] = useState([]); const [isLoading, setIsLoading] = useState(true); const [showDeletedSchemas, setShowDeletedSchemas] = useState(false); + const allowEditDisplayNamePermission = useMemo(() => { + return ( + !isVersionPage && + (permissions.databaseSchema.EditAll || + permissions.databaseSchema.EditDisplayName) + ); + }, [permissions, isVersionPage]); + const searchValue = useMemo(() => { const param = location.search; const searchData = QueryString.parse( @@ -160,6 +185,98 @@ export const DatabaseSchemaTable = ({ } }; + const handleDisplayNameUpdate = useCallback( + async (data: EntityName, id?: string) => { + try { + const schemaDetails = schemas.find((schema) => schema.id === id); + if (!schemaDetails) { + return; + } + const updatedData = { + ...schemaDetails, + displayName: data.displayName || undefined, + }; + const jsonPatch = compare(schemaDetails, updatedData); + await patchDatabaseSchemaDetails(schemaDetails.id ?? '', jsonPatch); + setSchemas((prevData) => + prevData.map((schema) => + schema.id === id + ? { ...schema, displayName: data.displayName } + : schema + ) + ); + } catch (error) { + showErrorToast(error as AxiosError); + } + }, + [schemas] + ); + + const schemaTableColumns: ColumnsType = useMemo( + () => [ + { + title: t('label.schema-name'), + dataIndex: 'name', + key: 'name', + width: 250, + render: (_, record: DatabaseSchema) => ( + + ), + }, + { + title: t('label.description'), + dataIndex: 'description', + key: 'description', + render: (text: string) => + text?.trim() ? ( + + ) : ( + + {t('label.no-entity', { entity: t('label.description') })} + + ), + }, + { + title: t('label.owner-plural'), + dataIndex: 'owners', + key: 'owners', + width: 120, + render: (owners: EntityReference[]) => + !isEmpty(owners) && owners.length > 0 ? ( + owners.map((owner: EntityReference) => getEntityName(owner)) + ) : ( + + {NO_DATA_PLACEHOLDER} + + ), + }, + { + title: t('label.usage'), + dataIndex: 'usageSummary', + key: 'usageSummary', + width: 120, + render: (text: UsageDetails) => + getUsagePercentile(text?.weeklyStats?.percentileRank ?? 0), + }, + ], + [handleDisplayNameUpdate, allowEditDisplayNamePermission] + ); + useEffect(() => { fetchDatabaseSchema(); }, [decodedDatabaseFQN, pageSize, showDeletedSchemas, isDatabaseDeleted]); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Modals/EntityNameModal/EntityNameModal.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/Modals/EntityNameModal/EntityNameModal.interface.ts index 4f57e3699588..2106081055e3 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Modals/EntityNameModal/EntityNameModal.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/Modals/EntityNameModal/EntityNameModal.interface.ts @@ -13,7 +13,7 @@ import { Rule } from 'antd/lib/form'; import { Constraint } from '../../../generated/entity/data/table'; -export type EntityName = { name: string; displayName?: string }; +export type EntityName = { name: string; displayName?: string; id?: string }; export type EntityNameWithAdditionFields = EntityName & { constraint: Constraint; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/DisplayName/DisplayName.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/common/DisplayName/DisplayName.interface.ts new file mode 100644 index 000000000000..b7a3f0f8218a --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/DisplayName/DisplayName.interface.ts @@ -0,0 +1,22 @@ +/* + * Copyright 2024 Collate. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { EntityName } from '../../Modals/EntityNameModal/EntityNameModal.interface'; + +export interface DisplayNameProps { + id: string; + name?: string; + displayName?: string; + link: string; + onEditDisplayName?: (data: EntityName, id?: string) => Promise; + allowRename?: boolean; +} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/DisplayName/DisplayName.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/DisplayName/DisplayName.test.tsx new file mode 100644 index 000000000000..5f6322bd41b8 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/DisplayName/DisplayName.test.tsx @@ -0,0 +1,102 @@ +/* + * Copyright 2024 Collate. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { act, fireEvent, render, screen } from '@testing-library/react'; +import React from 'react'; +import { MemoryRouter } from 'react-router-dom'; +import DisplayName from './DisplayName'; +import { DisplayNameProps } from './DisplayName.interface'; + +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + Link: jest + .fn() + .mockImplementation(({ children, ...props }) => ( + {children} + )), +})); + +jest.mock('../../../constants/constants', () => ({ + DE_ACTIVE_COLOR: '#BFBFBF', + ICON_DIMENSION: { width: 16, height: 16 }, +})); + +jest.mock('../../Modals/EntityNameModal/EntityNameModal.component', () => + jest.fn().mockImplementation(() =>

Mocked Modal

) +); + +const mockOnEditDisplayName = jest.fn(); + +const mockProps: DisplayNameProps = { + id: '1', + name: 'Sample Entity', + displayName: 'Sample Display Name', + link: '/entity/1', + allowRename: true, + onEditDisplayName: mockOnEditDisplayName, +}; + +describe('Test DisplayName Component', () => { + it('Should render the component with the display name', async () => { + await act(async () => { + render( + + + + ); + + const displayNameField = await screen.getByTestId('column-display-name'); + + expect(displayNameField).toBeInTheDocument(); + expect(displayNameField).toHaveTextContent('Sample Display Name'); + + const editButton = screen.queryByTestId('edit-displayName-button'); + + expect(editButton).toBeInTheDocument(); + }); + }); + + it('Should render the component with name when display name is empty', async () => { + await act(async () => { + render( + + + + ); + + const nameField = screen.getByTestId('column-name'); + + expect(nameField).toBeInTheDocument(); + expect(nameField).toHaveTextContent('Sample Entity'); + }); + }); + + it('Should open the edit modal on edit button click', async () => { + await act(async () => { + render( + + + + ); + const editButton = screen.getByTestId('edit-displayName-button'); + fireEvent.click(editButton); + + const nameField = await screen.findByTestId('column-name'); + + expect(nameField).toBeInTheDocument(); + + const displayNameField = await screen.findByTestId('column-display-name'); + + expect(displayNameField).toBeInTheDocument(); + }); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/DisplayName/DisplayName.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/DisplayName/DisplayName.tsx new file mode 100644 index 000000000000..5a8a1f23a2a7 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/DisplayName/DisplayName.tsx @@ -0,0 +1,103 @@ +/* + * Copyright 2024 Collate. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Button, Tooltip, Typography } from 'antd'; +import { AxiosError } from 'axios'; +import { isEmpty } from 'lodash'; +import React, { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Link } from 'react-router-dom'; +import { ReactComponent as IconEdit } from '../../../assets/svg/edit-new.svg'; +import { DE_ACTIVE_COLOR, ICON_DIMENSION } from '../../../constants/constants'; +import { showErrorToast } from '../../../utils/ToastUtils'; +import EntityNameModal from '../../Modals/EntityNameModal/EntityNameModal.component'; +import { EntityName } from '../../Modals/EntityNameModal/EntityNameModal.interface'; +import { DisplayNameProps } from './DisplayName.interface'; + +const DisplayName: React.FC = ({ + id, + name, + displayName, + onEditDisplayName, + link, + allowRename, +}) => { + const { t } = useTranslation(); + + const [isDisplayNameEditing, setIsDisplayNameEditing] = useState(false); + + const handleDisplayNameUpdate = async (data: EntityName) => { + setIsDisplayNameEditing(true); + try { + await onEditDisplayName?.(data, id); + } catch (error) { + showErrorToast(error as AxiosError); + } finally { + setIsDisplayNameEditing(false); + } + }; + + return ( +
+ + {isEmpty(displayName) ? ( + + {name} + + ) : ( + <> + {name} + + + {displayName} + + + + )} + + + {allowRename ? ( + +
+ ); +}; + +export default DisplayName; diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/DatabaseSchemaPage/SchemaTablesTab.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/DatabaseSchemaPage/SchemaTablesTab.tsx index d9ef74254263..cc397a63a3d9 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/DatabaseSchemaPage/SchemaTablesTab.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/DatabaseSchemaPage/SchemaTablesTab.tsx @@ -13,23 +13,29 @@ import { Col, Row, Switch, Typography } from 'antd'; import { ColumnsType } from 'antd/lib/table'; +import { AxiosError } from 'axios'; +import { compare } from 'fast-json-patch'; import { isEmpty, isUndefined } from 'lodash'; -import React, { useMemo } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; -import { Link } from 'react-router-dom'; +import DisplayName from '../../components/common/DisplayName/DisplayName'; import DescriptionV1 from '../../components/common/EntityDescription/DescriptionV1'; import ErrorPlaceHolder from '../../components/common/ErrorWithPlaceholder/ErrorPlaceHolder'; import NextPrevious from '../../components/common/NextPrevious/NextPrevious'; import { NextPreviousProps } from '../../components/common/NextPrevious/NextPrevious.interface'; import RichTextEditorPreviewer from '../../components/common/RichTextEditor/RichTextEditorPreviewer'; import TableAntd from '../../components/common/Table/Table'; +import { EntityName } from '../../components/Modals/EntityNameModal/EntityNameModal.interface'; +import { usePermissionProvider } from '../../context/PermissionProvider/PermissionProvider'; import { ERROR_PLACEHOLDER_TYPE } from '../../enums/common.enum'; import { EntityType } from '../../enums/entity.enum'; import { DatabaseSchema } from '../../generated/entity/data/databaseSchema'; import { Table } from '../../generated/entity/data/table'; import { UsePagingInterface } from '../../hooks/paging/usePaging'; +import { patchTableDetails } from '../../rest/tableAPI'; import entityUtilClassBase from '../../utils/EntityUtilClassBase'; import { getEntityName } from '../../utils/EntityUtils'; +import { showErrorToast } from '../../utils/ToastUtils'; interface SchemaTablesTabProps { databaseSchemaDetails: DatabaseSchema; @@ -69,6 +75,48 @@ function SchemaTablesTab({ pagingInfo, }: Readonly) { const { t } = useTranslation(); + const [localTableData, setLocalTableData] = useState([]); + + const { permissions } = usePermissionProvider(); + + const allowEditDisplayNamePermission = useMemo(() => { + return ( + !isVersionView && + (permissions.table.EditAll || permissions.table.EditDisplayName) + ); + }, [permissions, isVersionView]); + + const handleDisplayNameUpdate = useCallback( + async (data: EntityName, id?: string) => { + try { + const tableDetails = localTableData.find((table) => table.id === id); + if (!tableDetails) { + return; + } + const updatedData = { + ...tableDetails, + displayName: data.displayName || undefined, + }; + const jsonPatch = compare(tableDetails, updatedData); + await patchTableDetails(tableDetails.id, jsonPatch); + + setLocalTableData((prevData) => + prevData.map((table) => + table.id === id + ? { ...table, displayName: data.displayName } + : table + ) + ); + } catch (error) { + showErrorToast(error as AxiosError); + } + }, + [localTableData] + ); + + useEffect(() => { + setLocalTableData(tableData); + }, [tableData]); const tableColumn: ColumnsType = useMemo( () => [ @@ -79,17 +127,18 @@ function SchemaTablesTab({ width: 500, render: (_, record: Table) => { return ( -
- - {getEntityName(record)} - -
+ ); }, }, @@ -105,7 +154,7 @@ function SchemaTablesTab({ ), }, ], - [] + [handleDisplayNameUpdate, allowEditDisplayNamePermission] ); return ( @@ -158,7 +207,7 @@ function SchemaTablesTab({ bordered columns={tableColumn} data-testid="databaseSchema-tables" - dataSource={tableData} + dataSource={localTableData} loading={tableDataLoading} locale={{ emptyText: ( diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/DatabaseVersionPage/DatabaseVersionPage.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/DatabaseVersionPage/DatabaseVersionPage.tsx index dc7eef110c1f..84eeee904ffe 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/DatabaseVersionPage/DatabaseVersionPage.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/DatabaseVersionPage/DatabaseVersionPage.tsx @@ -209,7 +209,7 @@ function DatabaseVersionPage() { /> - + diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/ServiceDetailsPage/ServiceMainTabContent.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/ServiceDetailsPage/ServiceMainTabContent.tsx index b24b1f2af7dd..bf9643b01fe1 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/ServiceDetailsPage/ServiceMainTabContent.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/ServiceDetailsPage/ServiceMainTabContent.tsx @@ -13,9 +13,11 @@ import { Col, Row, Space, Switch, Table, Typography } from 'antd'; import { ColumnsType } from 'antd/lib/table'; +import { AxiosError } from 'axios'; +import { compare } from 'fast-json-patch'; import { isUndefined } from 'lodash'; import { EntityTags, ServiceTypes } from 'Models'; -import React, { useCallback, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useParams } from 'react-router-dom'; import DescriptionV1 from '../../components/common/EntityDescription/DescriptionV1'; @@ -25,7 +27,9 @@ import NextPrevious from '../../components/common/NextPrevious/NextPrevious'; import { NextPreviousProps } from '../../components/common/NextPrevious/NextPrevious.interface'; import ResizablePanels from '../../components/common/ResizablePanels/ResizablePanels'; import EntityRightPanel from '../../components/Entity/EntityRightPanel/EntityRightPanel'; +import { EntityName } from '../../components/Modals/EntityNameModal/EntityNameModal.interface'; import { COMMON_RESIZABLE_PANEL_CONFIG } from '../../constants/ResizablePanel.constants'; +import { usePermissionProvider } from '../../context/PermissionProvider/PermissionProvider'; import { OperationPermission } from '../../context/PermissionProvider/PermissionProvider.interface'; import { EntityType } from '../../enums/entity.enum'; import { DatabaseService } from '../../generated/entity/services/databaseService'; @@ -33,10 +37,14 @@ import { Paging } from '../../generated/type/paging'; import { UsePagingInterface } from '../../hooks/paging/usePaging'; import { useFqn } from '../../hooks/useFqn'; import { ServicesType } from '../../interface/service.interface'; -import { getServiceMainTabColumns } from '../../utils/ServiceMainTabContentUtils'; +import { + callServicePatchAPI, + getServiceMainTabColumns, +} from '../../utils/ServiceMainTabContentUtils'; import { getEntityTypeFromServiceCategory } from '../../utils/ServiceUtils'; import { getTagsWithoutTier, getTierTags } from '../../utils/TableUtils'; import { createTagObject } from '../../utils/TagsUtils'; +import { showErrorToast } from '../../utils/ToastUtils'; import { ServicePageData } from './ServiceDetailsPage'; interface ServiceMainTabContentProps { @@ -53,6 +61,7 @@ interface ServiceMainTabContentProps { pagingHandler: NextPreviousProps['pagingHandler']; saveUpdatedServiceData: (updatedData: ServicesType) => Promise; pagingInfo: UsePagingInterface; + isVersionPage?: boolean; } function ServiceMainTabContent({ @@ -69,6 +78,7 @@ function ServiceMainTabContent({ serviceDetails, saveUpdatedServiceData, pagingInfo, + isVersionPage = false, }: Readonly) { const { t } = useTranslation(); const { serviceCategory } = useParams<{ @@ -76,7 +86,10 @@ function ServiceMainTabContent({ }>(); const { fqn: serviceFQN } = useFqn(); + const { permissions } = usePermissionProvider(); + const [isEdit, setIsEdit] = useState(false); + const [pageData, setPageData] = useState([]); const tier = getTierTags(serviceDetails?.tags ?? []); const tags = getTagsWithoutTier(serviceDetails?.tags ?? []); @@ -131,9 +144,69 @@ function ServiceMainTabContent({ setIsEdit(false); }; + const handleDisplayNameUpdate = useCallback( + async (entityData: EntityName, id?: string) => { + try { + const pageDataDetails = pageData.find((data) => data.id === id); + if (!pageDataDetails) { + return; + } + const updatedData = { + ...pageDataDetails, + displayName: entityData.displayName || undefined, + }; + const jsonPatch = compare(pageDataDetails, updatedData); + await callServicePatchAPI( + serviceCategory, + pageDataDetails.id, + jsonPatch + ); + setPageData((prevData) => + prevData.map((data) => + data.id === id + ? { ...data, displayName: entityData.displayName } + : data + ) + ); + } catch (error) { + showErrorToast(error as AxiosError); + } + }, + [pageData, serviceCategory] + ); + + const editDisplayNamePermission = useMemo(() => { + if (isVersionPage) { + return false; + } + + const servicePermissions = { + databaseServices: permissions.databaseService, + messagingServices: permissions.messagingService, + dashboardServices: permissions.dashboardService, + pipelineServices: permissions.pipelineService, + mlmodelServices: permissions.mlmodelService, + storageServices: permissions.storageService, + searchServices: permissions.searchService, + apiServices: permissions.apiService, + }; + + const currentPermission = + servicePermissions[serviceCategory as keyof typeof servicePermissions]; + + return ( + currentPermission?.EditAll || currentPermission?.EditDisplayName || false + ); + }, [permissions, serviceCategory, isVersionPage]); + const tableColumn: ColumnsType = useMemo( - () => getServiceMainTabColumns(serviceCategory), - [serviceCategory] + () => + getServiceMainTabColumns( + serviceCategory, + editDisplayNamePermission, + handleDisplayNameUpdate + ), + [serviceCategory, handleDisplayNameUpdate, editDisplayNamePermission] ); const entityType = useMemo( @@ -160,6 +233,10 @@ function ServiceMainTabContent({ [servicePermission, serviceDetails] ); + useEffect(() => { + setPageData(data); + }, [data]); + return ( @@ -210,7 +287,7 @@ function ServiceMainTabContent({ bordered columns={tableColumn} data-testid="service-children-table" - dataSource={data} + dataSource={pageData} locale={{ emptyText: , }} diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/ServiceMainTabContentUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/ServiceMainTabContentUtils.tsx index c629f20711fd..2c58500e1216 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/ServiceMainTabContentUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/ServiceMainTabContentUtils.tsx @@ -17,44 +17,51 @@ import { t } from 'i18next'; import { isUndefined } from 'lodash'; import { ServiceTypes } from 'Models'; import React from 'react'; -import { Link } from 'react-router-dom'; +import DisplayName from '../components/common/DisplayName/DisplayName'; import { OwnerLabel } from '../components/common/OwnerLabel/OwnerLabel.component'; import RichTextEditorPreviewer from '../components/common/RichTextEditor/RichTextEditorPreviewer'; +import { EntityName } from '../components/Modals/EntityNameModal/EntityNameModal.interface'; import TagsViewer from '../components/Tag/TagsViewer/TagsViewer'; import { NO_DATA_PLACEHOLDER } from '../constants/constants'; import { ServiceCategory } from '../enums/service.enum'; import { Database } from '../generated/entity/data/database'; import { Pipeline } from '../generated/entity/data/pipeline'; import { ServicePageData } from '../pages/ServiceDetailsPage/ServiceDetailsPage'; -import { getEntityName } from './EntityUtils'; +import { patchApiCollection } from '../rest/apiCollectionsAPI'; +import { patchDashboardDetails } from '../rest/dashboardAPI'; +import { patchDatabaseDetails } from '../rest/databaseAPI'; +import { patchMlModelDetails } from '../rest/mlModelAPI'; +import { patchPipelineDetails } from '../rest/pipelineAPI'; +import { patchSearchIndexDetails } from '../rest/SearchIndexAPI'; +import { patchContainerDetails } from '../rest/storageAPI'; +import { patchTopicDetails } from '../rest/topicsAPI'; import { getLinkForFqn } from './ServiceUtils'; import { getUsagePercentile } from './TableUtils'; export const getServiceMainTabColumns = ( - serviceCategory: ServiceTypes + serviceCategory: ServiceTypes, + editDisplayNamePermission?: boolean, + handleDisplayNameUpdate?: ( + entityData: EntityName, + id?: string + ) => Promise ): ColumnsType => [ { title: t('label.name'), dataIndex: 'name', key: 'name', width: 280, - render: (_, record: ServicePageData) => { - return ( - - - {getEntityName(record)} - - - ); - }, + render: (_, record: ServicePageData) => ( + + ), }, { title: t('label.description'), @@ -123,3 +130,30 @@ export const getServiceMainTabColumns = ( ] : []), ]; + +export const callServicePatchAPI = async ( + serviceCategory: ServiceTypes, + id: string, + jsonPatch: any +) => { + switch (serviceCategory) { + case ServiceCategory.DATABASE_SERVICES: + return await patchDatabaseDetails(id, jsonPatch); + case ServiceCategory.MESSAGING_SERVICES: + return await patchTopicDetails(id, jsonPatch); + case ServiceCategory.DASHBOARD_SERVICES: + return await patchDashboardDetails(id, jsonPatch); + case ServiceCategory.PIPELINE_SERVICES: + return await patchPipelineDetails(id, jsonPatch); + case ServiceCategory.ML_MODEL_SERVICES: + return await patchMlModelDetails(id, jsonPatch); + case ServiceCategory.STORAGE_SERVICES: + return await patchContainerDetails(id, jsonPatch); + case ServiceCategory.SEARCH_SERVICES: + return await patchSearchIndexDetails(id, jsonPatch); + case ServiceCategory.API_SERVICES: + return await patchApiCollection(id, jsonPatch); + default: + return; + } +};