From dccc139aad30f50eb3c61c9e41d6fde842469caa Mon Sep 17 00:00:00 2001 From: Sachin Chaurasiya Date: Fri, 10 Mar 2023 13:25:24 +0530 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8feat(ui)#10271:UI=20-=20Object=20Stora?= =?UTF-8?q?ge=20Service=20Part-2=20(#10419)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ✨feat(ui)#10271:UI - Object Storage Service * chore: add objectStoreService in permission resource enum * chore: add support for connection config form for object store service * chore: add support for service connection details schema for object service * chore: change objectStores to objectstores * chore: add object stores in global settings * chore: update objectstore locals * chore: change objectStoreServices to objectstoreServices * chore: add support for page header for object stores * chore: fetch containers for object store service * chore: fix connection config is not showing up for object store service * chore: update getLinkForFqn for object store service * fix: add object store service in delete widget modal * chore: add object store service logo * chore: update object store service label * chore: fix routing of object store service * test: add unit test for object store service * fix: missing tags column header * test: add unit test for global setting left panel * test: update global setting left panel test * chore: update svg to take the current color * address comments * ✨feat(ui)#10271:UI - Object Storage Service Part-2 * chore: add support for container children * chore: add support for data model component * chore: add support for adding/updating description * fix: read serviceType directly from entity * chore: add support for follow and unFollow container * chore: add support for update/remove owner and tier * chore: add support for updating/removing tags of container * fix: version is not updating in real time * chore: add support for restoring the container * chore: add support for lineage * chore: add support for custom properties * chore: add support for CRUD for custom attributes * chore: add support for container nth level in breadcrumb * fix: unit test * chore: locale update * chore: add support for updating data models * chore: add object store service icon * address previous PR comments * test: add unit test * chore: fix label issue * chore: remove duplicate codes * chore: add beta tag for object store service * fix: tags source issue * address comments --- .../src/assets/img/service-icon-amazon-s3.svg | 34 + .../ui/src/assets/img/service-icon-gcs.png | Bin 0 -> 37927 bytes .../src/assets/img/service-icon-ms-azure.png | Bin 0 -> 85496 bytes .../ContainerChildren.test.tsx | 83 ++ .../ContainerChildren/ContainerChildren.tsx | 82 ++ .../ContainerDataModel.interface.ts | 28 + .../ContainerDataModel.test.tsx | 175 ++++ .../ContainerDataModel/ContainerDataModel.tsx | 283 +++++++ .../GlobalSetting/GlobalSettingLeftPanel.tsx | 16 +- .../PermissionProvider.interface.ts | 1 + .../CustomPropertyTable.interface.ts | 8 +- .../CustomPropertyTable.test.tsx | 8 +- .../router/AuthenticatedAppRouter.tsx | 10 + .../ui/src/constants/PageHeaders.constant.ts | 8 + .../ui/src/constants/Services.constant.ts | 6 + .../resources/ui/src/constants/constants.ts | 5 + .../resources/ui/src/enums/entity.enum.ts | 1 + .../ui/src/locale/languages/en-us.json | 2 + .../ui/src/locale/languages/fr-fr.json | 2 + .../ui/src/locale/languages/zh-cn.json | 2 + .../src/pages/ContainerPage/ContainerPage.tsx | 751 ++++++++++++++++++ .../CustomPropertiesPageV1.tsx | 3 + .../resources/ui/src/pages/service/index.tsx | 11 +- .../resources/ui/src/rest/objectStoreAPI.ts | 62 ++ .../ui/src/styles/components/menu.less | 10 + .../resources/ui/src/utils/CommonUtils.tsx | 5 +- .../ui/src/utils/ContainerDetailUtils.test.ts | 202 +++++ .../ui/src/utils/ContainerDetailUtils.ts | 112 +++ .../ui/src/utils/GlobalSettingsUtils.tsx | 46 +- .../resources/ui/src/utils/ServiceUtils.tsx | 13 + .../resources/ui/src/utils/TableUtils.tsx | 4 + 31 files changed, 1937 insertions(+), 36 deletions(-) create mode 100644 openmetadata-ui/src/main/resources/ui/src/assets/img/service-icon-amazon-s3.svg create mode 100644 openmetadata-ui/src/main/resources/ui/src/assets/img/service-icon-gcs.png create mode 100644 openmetadata-ui/src/main/resources/ui/src/assets/img/service-icon-ms-azure.png create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/ContainerDetail/ContainerChildren/ContainerChildren.test.tsx create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/ContainerDetail/ContainerChildren/ContainerChildren.tsx create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/ContainerDetail/ContainerDataModel/ContainerDataModel.interface.ts create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/ContainerDetail/ContainerDataModel/ContainerDataModel.test.tsx create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/ContainerDetail/ContainerDataModel/ContainerDataModel.tsx create mode 100644 openmetadata-ui/src/main/resources/ui/src/pages/ContainerPage/ContainerPage.tsx create mode 100644 openmetadata-ui/src/main/resources/ui/src/utils/ContainerDetailUtils.test.ts create mode 100644 openmetadata-ui/src/main/resources/ui/src/utils/ContainerDetailUtils.ts diff --git a/openmetadata-ui/src/main/resources/ui/src/assets/img/service-icon-amazon-s3.svg b/openmetadata-ui/src/main/resources/ui/src/assets/img/service-icon-amazon-s3.svg new file mode 100644 index 00000000000..3f63be51fa2 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/assets/img/service-icon-amazon-s3.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + diff --git a/openmetadata-ui/src/main/resources/ui/src/assets/img/service-icon-gcs.png b/openmetadata-ui/src/main/resources/ui/src/assets/img/service-icon-gcs.png new file mode 100644 index 0000000000000000000000000000000000000000..b3957dfd55ce3b0275b4e6421cb192fa6704172b GIT binary patch literal 37927 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 literal 0 HcmV?d00001 diff --git a/openmetadata-ui/src/main/resources/ui/src/assets/img/service-icon-ms-azure.png b/openmetadata-ui/src/main/resources/ui/src/assets/img/service-icon-ms-azure.png new file mode 100644 index 0000000000000000000000000000000000000000..f1200c723183e142184523655516dfa3d74498f0 GIT binary patch literal 85496 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 { + it('Should render table with correct columns', () => { + render( + + + + ); + + expect(screen.getByTestId('container-list-table')).toBeInTheDocument(); + expect(screen.getByText('label.name')).toBeInTheDocument(); + expect(screen.getByText('label.description')).toBeInTheDocument(); + }); + + it('Should render container names as links', () => { + render( + + + + ); + + const containerNameLinks = screen.getAllByTestId('container-name'); + + expect(containerNameLinks).toHaveLength(2); + + containerNameLinks.forEach((link, index) => { + expect(link).toHaveAttribute( + 'href', + `/container/${mockChildrenList[index].fullyQualifiedName}` + ); + expect(link).toHaveTextContent(mockChildrenList[index].name); + }); + }); + + it('Should render container descriptions as rich text', () => { + render( + + + + ); + + const richTextPreviewers = screen.getAllByTestId('viewer-container'); + + expect(richTextPreviewers).toHaveLength(2); + + richTextPreviewers.forEach((previewer, index) => { + expect(previewer).toHaveTextContent(mockChildrenList[index].description); + }); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ContainerDetail/ContainerChildren/ContainerChildren.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ContainerDetail/ContainerChildren/ContainerChildren.tsx new file mode 100644 index 00000000000..8fca9c354b4 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/ContainerDetail/ContainerChildren/ContainerChildren.tsx @@ -0,0 +1,82 @@ +/* + * Copyright 2023 Collate. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Typography } from 'antd'; +import Table, { ColumnsType } from 'antd/lib/table'; +import RichTextEditorPreviewer from 'components/common/rich-text-editor/RichTextEditorPreviewer'; +import { Container } from 'generated/entity/data/container'; +import { EntityReference } from 'generated/type/entityReference'; +import React, { FC, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Link } from 'react-router-dom'; +import { getEntityName } from 'utils/CommonUtils'; +import { getContainerDetailPath } from 'utils/ContainerDetailUtils'; + +interface ContainerChildrenProps { + childrenList: Container['children']; +} + +const ContainerChildren: FC = ({ childrenList }) => { + const { t } = useTranslation(); + + const columns: ColumnsType = useMemo( + () => [ + { + title: t('label.name'), + dataIndex: 'name', + width: '200px', + key: 'name', + render: (_, record) => ( + + {getEntityName(record)} + + ), + }, + { + title: t('label.description'), + dataIndex: 'description', + key: 'description', + render: (description: EntityReference['description']) => ( + <> + {description ? ( + + ) : ( + + {t('label.no-entity', { + entity: t('label.description'), + })} + + )} + + ), + }, + ], + [] + ); + + return ( + + ); +}; + +export default ContainerChildren; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ContainerDetail/ContainerDataModel/ContainerDataModel.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/ContainerDetail/ContainerDataModel/ContainerDataModel.interface.ts new file mode 100644 index 00000000000..f7f2e3cc904 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/ContainerDetail/ContainerDataModel/ContainerDataModel.interface.ts @@ -0,0 +1,28 @@ +/* + * Copyright 2023 Collate. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Container } from 'generated/entity/data/container'; +import { ReactNode } from 'react'; + +export type CellRendered = ( + value: T[K], + record: T, + index: number +) => ReactNode; + +export interface ContainerDataModelProps { + dataModel: Container['dataModel']; + hasDescriptionEditAccess: boolean; + hasTagEditAccess: boolean; + isReadOnly: boolean; + onUpdate: (updatedDataModel: Container['dataModel']) => Promise; +} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ContainerDetail/ContainerDataModel/ContainerDataModel.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ContainerDetail/ContainerDataModel/ContainerDataModel.test.tsx new file mode 100644 index 00000000000..a478f3dff3c --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/ContainerDetail/ContainerDataModel/ContainerDataModel.test.tsx @@ -0,0 +1,175 @@ +/* + * Copyright 2023 Collate. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + act, + findByTestId, + findByText, + queryByTestId, + render, + screen, +} from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { Column } from 'generated/entity/data/container'; +import React from 'react'; +import ContainerDataModel from './ContainerDataModel'; + +const props = { + dataModel: { + isPartitioned: false, + columns: [ + { + name: 'department_id', + dataType: 'NUMERIC', + dataTypeDisplay: 'numeric', + description: + 'The ID of the department. This column is the primary key for this table.', + fullyQualifiedName: 's3_object_store_sample.finance.department_id', + tags: [], + constraint: 'PRIMARY_KEY', + ordinalPosition: 1, + }, + { + name: 'budget_total_value', + dataType: 'NUMERIC', + dataTypeDisplay: 'numeric', + description: "The department's budget for the current year.", + fullyQualifiedName: 's3_object_store_sample.finance.budget_total_value', + tags: [], + ordinalPosition: 2, + }, + { + name: 'notes', + dataType: 'VARCHAR', + dataLength: 100, + dataTypeDisplay: 'varchar', + description: 'Notes concerning sustainability for the budget.', + fullyQualifiedName: 's3_object_store_sample.finance.notes', + tags: [], + ordinalPosition: 3, + }, + { + name: 'budget_executor', + dataType: 'VARCHAR', + dataTypeDisplay: 'varchar', + description: 'The responsible finance lead for the budget execution', + fullyQualifiedName: 's3_object_store_sample.finance.budget_executor', + tags: [], + ordinalPosition: 4, + }, + ] as Column[], + }, + hasDescriptionEditAccess: true, + hasTagEditAccess: true, + isReadOnly: false, + onUpdate: jest.fn(), +}; + +jest.mock('utils/TagsUtils', () => ({ + fetchTagsAndGlossaryTerms: jest.fn().mockReturnValue([]), +})); + +jest.mock('utils/ContainerDetailUtils', () => ({ + updateContainerColumnDescription: jest.fn(), + updateContainerColumnTags: jest.fn(), +})); + +jest.mock('components/common/rich-text-editor/RichTextEditorPreviewer', () => + jest + .fn() + .mockReturnValue( +
Description Preview
+ ) +); + +jest.mock( + 'components/Modals/ModalWithMarkdownEditor/ModalWithMarkdownEditor', + () => ({ + ModalWithMarkdownEditor: jest + .fn() + .mockReturnValue(
Editor Modal
), + }) +); + +jest.mock('components/Tag/TagsContainer/tags-container', () => + jest + .fn() + .mockReturnValue(
Tag Container
) +); + +jest.mock('components/Tag/TagsViewer/tags-viewer', () => + jest.fn().mockReturnValue(
Tag Viewer
) +); + +describe('ContainerDataModel', () => { + it('Should render the Container data model component', async () => { + render(); + + const containerDataModel = await screen.findByTestId( + 'container-data-model-table' + ); + const rows = await screen.findAllByRole('row'); + + const row1 = rows[1]; + + expect(containerDataModel).toBeInTheDocument(); + + // should render header row and content row + expect(rows).toHaveLength(5); + + const name = await findByText(row1, 'department_id'); + const dataType = await findByText(row1, 'numeric'); + const description = await findByText(row1, 'Description Preview'); + const tags = await findByTestId(row1, 'tag-container'); + + expect(name).toBeInTheDocument(); + expect(dataType).toBeInTheDocument(); + expect(description).toBeInTheDocument(); + expect(tags).toBeInTheDocument(); + }); + + it('On edit description button click modal editor should render', async () => { + render(); + + const rows = await screen.findAllByRole('row'); + + const row1 = rows[1]; + + const editDescriptionButton = await findByTestId(row1, 'edit-button'); + + expect(editDescriptionButton).toBeInTheDocument(); + + await act(async () => { + userEvent.click(editDescriptionButton); + }); + + expect(await screen.findByTestId('editor-modal')).toBeInTheDocument(); + }); + + it('Should not render the edit action if isReadOnly', async () => { + render( + + ); + + const rows = await screen.findAllByRole('row'); + + const row1 = rows[1]; + + const editDescriptionButton = queryByTestId(row1, 'edit-button'); + + expect(editDescriptionButton).toBeNull(); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ContainerDetail/ContainerDataModel/ContainerDataModel.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ContainerDetail/ContainerDataModel/ContainerDataModel.tsx new file mode 100644 index 00000000000..f2d5db145f1 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/ContainerDetail/ContainerDataModel/ContainerDataModel.tsx @@ -0,0 +1,283 @@ +/* + * Copyright 2023 Collate. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Button, Popover, Space, Typography } from 'antd'; +import Table, { ColumnsType } from 'antd/lib/table'; +import RichTextEditorPreviewer from 'components/common/rich-text-editor/RichTextEditorPreviewer'; +import TagsContainer from 'components/Tag/TagsContainer/tags-container'; +import TagsViewer from 'components/Tag/TagsViewer/tags-viewer'; +import { Column } from 'generated/entity/data/container'; +import { cloneDeep, isEmpty, isUndefined, toLower } from 'lodash'; +import { EntityTags, TagOption } from 'Models'; +import React, { FC, useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { + updateContainerColumnDescription, + updateContainerColumnTags, +} from 'utils/ContainerDetailUtils'; +import { getTableExpandableConfig } from 'utils/TableUtils'; +import { fetchTagsAndGlossaryTerms } from 'utils/TagsUtils'; +import { + CellRendered, + ContainerDataModelProps, +} from './ContainerDataModel.interface'; + +import { ReactComponent as EditIcon } from 'assets/svg/ic-edit.svg'; +import { ModalWithMarkdownEditor } from 'components/Modals/ModalWithMarkdownEditor/ModalWithMarkdownEditor'; +import { getEntityName } from 'utils/CommonUtils'; + +const ContainerDataModel: FC = ({ + dataModel, + hasDescriptionEditAccess, + hasTagEditAccess, + isReadOnly, + onUpdate, +}) => { + const { t } = useTranslation(); + + const [editContainerColumnDescription, setEditContainerColumnDescription] = + useState(); + const [editContainerColumnTags, setEditContainerColumnTags] = + useState(); + + const [tagList, setTagList] = useState([]); + const [isTagLoading, setIsTagLoading] = useState(false); + const [tagFetchFailed, setTagFetchFailed] = useState(false); + + const fetchTags = async () => { + setIsTagLoading(true); + try { + const tagsAndTerms = await fetchTagsAndGlossaryTerms(); + setTagList(tagsAndTerms); + } catch (error) { + setTagList([]); + setTagFetchFailed(true); + } finally { + setIsTagLoading(false); + } + }; + + const handleFieldTagsChange = async (selectedTags: EntityTags[] = []) => { + if (!isUndefined(editContainerColumnTags)) { + const newSelectedTags: TagOption[] = selectedTags.map((tag) => ({ + fqn: tag.tagFQN, + source: tag.source, + })); + + const containerDataModel = cloneDeep(dataModel); + + updateContainerColumnTags( + containerDataModel?.columns, + editContainerColumnTags?.name, + newSelectedTags + ); + + await onUpdate(containerDataModel); + } + setEditContainerColumnTags(undefined); + }; + + const handleAddTagClick = (record: Column) => { + if (isUndefined(editContainerColumnTags)) { + setEditContainerColumnTags(record); + // Fetch tags and terms only once + if (tagList.length === 0 || tagFetchFailed) { + fetchTags(); + } + } + }; + + const handleContainerColumnDescriptionChange = async ( + updatedDescription: string + ) => { + if (!isUndefined(editContainerColumnDescription)) { + const containerDataModel = cloneDeep(dataModel); + updateContainerColumnDescription( + containerDataModel?.columns, + editContainerColumnDescription?.name, + updatedDescription + ); + await onUpdate(containerDataModel); + } + setEditContainerColumnDescription(undefined); + }; + + const renderContainerColumnDescription: CellRendered = + (description, record, index) => { + return ( + + <> + {description ? ( + + ) : ( + + {t('label.no-entity', { + entity: t('label.description'), + })} + + )} + + {isReadOnly && !hasDescriptionEditAccess ? null : ( +
(), + rowExpandable: (record) => !isEmpty(record.children), + }} + pagination={false} + size="small" + /> + {editContainerColumnDescription && ( + setEditContainerColumnDescription(undefined)} + onSave={handleContainerColumnDescriptionChange} + /> + )} + + ); +}; + +export default ContainerDataModel; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/GlobalSetting/GlobalSettingLeftPanel.tsx b/openmetadata-ui/src/main/resources/ui/src/components/GlobalSetting/GlobalSettingLeftPanel.tsx index b0976a17999..6fbc1301997 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/GlobalSetting/GlobalSettingLeftPanel.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/GlobalSetting/GlobalSettingLeftPanel.tsx @@ -43,15 +43,13 @@ const GlobalSettingLeftPanel = () => { () => getGlobalSettingsMenuWithPermission(permissions, isAdminUser).reduce( (acc: ItemType[], curr: MenuList) => { - const menuItem = getGlobalSettingMenuItem( - curr.category, - camelCase(curr.category), - '', - '', - curr.items, - 'group', - curr.isBeta - ); + const menuItem = getGlobalSettingMenuItem({ + label: curr.category, + key: camelCase(curr.category), + children: curr.items, + type: 'group', + isBeta: curr.isBeta, + }); if (menuItem.children?.length) { return [...acc, menuItem]; } else { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/PermissionProvider/PermissionProvider.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/PermissionProvider/PermissionProvider.interface.ts index e3f8f8249a1..e2c44011400 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/PermissionProvider/PermissionProvider.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/PermissionProvider/PermissionProvider.interface.ts @@ -63,6 +63,7 @@ export enum ResourceEntity { USER = 'user', WEBHOOK = 'webhook', OBJECT_STORE_SERVICE = 'objectStoreService', + CONTAINER = 'container', } export interface PermissionContextType { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/CustomPropertyTable/CustomPropertyTable.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/common/CustomPropertyTable/CustomPropertyTable.interface.ts index c9c865ed245..c0dd362d30b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/CustomPropertyTable/CustomPropertyTable.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/CustomPropertyTable/CustomPropertyTable.interface.ts @@ -11,6 +11,7 @@ * limitations under the License. */ +import { Container } from 'generated/entity/data/container'; import { EntityType } from '../../../enums/entity.enum'; import { Dashboard } from '../../../generated/entity/data/dashboard'; import { Mlmodel } from '../../../generated/entity/data/mlmodel'; @@ -18,7 +19,12 @@ import { Pipeline } from '../../../generated/entity/data/pipeline'; import { Table } from '../../../generated/entity/data/table'; import { Topic } from '../../../generated/entity/data/topic'; -export type EntityDetails = Table & Topic & Dashboard & Pipeline & Mlmodel; +export type EntityDetails = Table & + Topic & + Dashboard & + Pipeline & + Mlmodel & + Container; export interface CustomPropertyProps { entityDetails: EntityDetails; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/CustomPropertyTable/CustomPropertyTable.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/CustomPropertyTable/CustomPropertyTable.test.tsx index 22610507008..811c674f5b1 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/CustomPropertyTable/CustomPropertyTable.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/CustomPropertyTable/CustomPropertyTable.test.tsx @@ -20,12 +20,8 @@ import { import React from 'react'; import { getTypeByFQN } from 'rest/metadataTypeAPI'; import { EntityType } from '../../../enums/entity.enum'; -import { Dashboard } from '../../../generated/entity/data/dashboard'; -import { Mlmodel } from '../../../generated/entity/data/mlmodel'; -import { Pipeline } from '../../../generated/entity/data/pipeline'; -import { Table } from '../../../generated/entity/data/table'; -import { Topic } from '../../../generated/entity/data/topic'; import { CustomPropertyTable } from './CustomPropertyTable'; +import { EntityDetails } from './CustomPropertyTable.interface'; const mockCustomProperties = [ { @@ -71,7 +67,7 @@ jest.mock('rest/metadataTypeAPI', () => ({ ), })); -const mockTableDetails = {} as Table & Topic & Dashboard & Pipeline & Mlmodel; +const mockTableDetails = {} as EntityDetails; const handleExtensionUpdate = jest.fn(); const mockProp = { diff --git a/openmetadata-ui/src/main/resources/ui/src/components/router/AuthenticatedAppRouter.tsx b/openmetadata-ui/src/main/resources/ui/src/components/router/AuthenticatedAppRouter.tsx index bc3e44ed1d4..1f965cb6c98 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/router/AuthenticatedAppRouter.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/router/AuthenticatedAppRouter.tsx @@ -216,6 +216,10 @@ const AddTestSuitePage = withSuspenseFallback( React.lazy(() => import('pages/TestSuitePage/TestSuiteStepper')) ); +const ContainerPage = withSuspenseFallback( + React.lazy(() => import('pages/ContainerPage/ContainerPage')) +); + const AuthenticatedAppRouter: FunctionComponent = () => { const { permissions } = usePermissionProvider(); @@ -346,6 +350,12 @@ const AuthenticatedAppRouter: FunctionComponent = () => { component={PipelineDetailsPage} path={ROUTES.PIPELINE_DETAILS} /> + + = { dashboards: 'dashboard', pipelines: 'pipeline', mlmodels: 'mlmodel', + containers: 'container', }; export const VALIDATE_MESSAGES = { diff --git a/openmetadata-ui/src/main/resources/ui/src/enums/entity.enum.ts b/openmetadata-ui/src/main/resources/ui/src/enums/entity.enum.ts index 24479e7321d..279b2fb6e78 100644 --- a/openmetadata-ui/src/main/resources/ui/src/enums/entity.enum.ts +++ b/openmetadata-ui/src/main/resources/ui/src/enums/entity.enum.ts @@ -40,6 +40,7 @@ export enum EntityType { DATA_INSIGHT_CHART = 'dataInsightChart', KPI = 'kpi', ALERT = 'alert', + CONTAINER = 'container', } export enum AssetsType { diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json index fa538d29979..51a94f16256 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json @@ -92,6 +92,7 @@ "chart-entity": "Chart {{entity}}", "chart-plural": "Charts", "check-status": "Check status", + "children": "Children", "claim-ownership": "Claim Ownership", "classification": "Classification", "classification-lowercase": "classification", @@ -898,6 +899,7 @@ "dbt-result-file-path": "dbt run results file path to extract the test results information.", "dbt-run-result-http-path-message": "dbt runs the results on an http path to extract the test results.", "deeply-understand-table-relations-message": "Deeply understand table relations; thanks to column-level lineage.", + "define-custom-property-for-entity": "Define custom properties for {{entity}} to serve your organizational needs.", "delete-action-description": "Deleting this {{entityType}} will permanently remove its metadata from OpenMetadata.", "delete-entity-permanently": "Once you delete this {{entityType}}, it will be removed permanently.", "delete-entity-type-action-description": "Deleting this {{entityType}} will permanently remove its metadata from OpenMetadata.", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/fr-fr.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/fr-fr.json index 55e216263cf..47739066ae9 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/fr-fr.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/fr-fr.json @@ -92,6 +92,7 @@ "chart-entity": "Chart {{entity}}", "chart-plural": "Charts", "check-status": "Check status", + "children": "Children", "claim-ownership": "Claim Ownership", "classification": "Classification", "classification-lowercase": "classification", @@ -898,6 +899,7 @@ "dbt-result-file-path": "dbt run results file path to extract the test results information.", "dbt-run-result-http-path-message": "dbt runs the results on an http path to extract the test results.", "deeply-understand-table-relations-message": "Comprenez en profondeur la relation entre vos tables avec la traçabilité au niveau de la colonne.", + "define-custom-property-for-entity": "Define custom properties for {{entity}} to serve your organizational needs.", "delete-action-description": "Supprimer cette {{entityType}} supprimera de manière permanente les métadonnées dans OpenMetadata.", "delete-entity-permanently": "Une fois que vous supprimez {{entityType}}, il sera supprimé de manière permanente", "delete-entity-type-action-description": "Deleting this {{entityType}} will permanently remove its metadata from OpenMetadata.", diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/zh-cn.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/zh-cn.json index 23235dcd55d..d6c8d3711f0 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/zh-cn.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/zh-cn.json @@ -92,6 +92,7 @@ "chart-entity": "图表 {{entity}}", "chart-plural": "图表", "check-status": "Check status", + "children": "Children", "claim-ownership": "Claim Ownership", "classification": "类别", "classification-lowercase": "类别", @@ -898,6 +899,7 @@ "dbt-result-file-path": "dbt run results file path to extract the test results information.", "dbt-run-result-http-path-message": "dbt run results http path to extract the test results information.", "deeply-understand-table-relations-message": "Deeply understand table relations; thanks to column-level lineage.", + "define-custom-property-for-entity": "Define custom properties for {{entity}} to serve your organizational needs.", "delete-action-description": "Deleting this {{entityType}} will permanently remove its metadata from OpenMetadata.", "delete-entity-permanently": "Once you delete this {{entityType}}, it will be removed permanently", "delete-entity-type-action-description": "Deleting this {{entityType}} will permanently remove its metadata from OpenMetadata.", diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/ContainerPage/ContainerPage.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/ContainerPage/ContainerPage.tsx new file mode 100644 index 00000000000..5d411dd78ab --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/pages/ContainerPage/ContainerPage.tsx @@ -0,0 +1,751 @@ +/* + * Copyright 2023 Collate. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Card, Col, Row, Tabs } from 'antd'; +import AppState from 'AppState'; +import { AxiosError } from 'axios'; +import { CustomPropertyTable } from 'components/common/CustomPropertyTable/CustomPropertyTable'; +import { CustomPropertyProps } from 'components/common/CustomPropertyTable/CustomPropertyTable.interface'; +import Description from 'components/common/description/Description'; +import EntityPageInfo from 'components/common/entityPageInfo/EntityPageInfo'; +import ErrorPlaceHolder from 'components/common/error-with-placeholder/ErrorPlaceHolder'; +import ContainerChildren from 'components/ContainerDetail/ContainerChildren/ContainerChildren'; +import ContainerDataModel from 'components/ContainerDetail/ContainerDataModel/ContainerDataModel'; +import PageContainerV1 from 'components/containers/PageContainerV1'; +import EntityLineageComponent from 'components/EntityLineage/EntityLineage.component'; +import { + Edge, + EdgeData, + LeafNodes, + LineagePos, + LoadingNodeState, +} from 'components/EntityLineage/EntityLineage.interface'; +import Loader from 'components/Loader/Loader'; +import { usePermissionProvider } from 'components/PermissionProvider/PermissionProvider'; +import { + OperationPermission, + ResourceEntity, +} from 'components/PermissionProvider/PermissionProvider.interface'; +import { FQN_SEPARATOR_CHAR } from 'constants/char.constants'; +import { getServiceDetailsPath } from 'constants/constants'; +import { ENTITY_CARD_CLASS } from 'constants/entity.constants'; +import { NO_PERMISSION_TO_VIEW } from 'constants/HelperTextUtil'; +import { EntityInfo, EntityType } from 'enums/entity.enum'; +import { ServiceCategory } from 'enums/service.enum'; +import { OwnerType } from 'enums/user.enum'; +import { compare } from 'fast-json-patch'; +import { Container } from 'generated/entity/data/container'; +import { EntityLineage } from 'generated/type/entityLineage'; +import { EntityReference } from 'generated/type/entityReference'; +import { LabelType, State, TagSource } from 'generated/type/tagLabel'; +import { isUndefined, omitBy } from 'lodash'; +import { observer } from 'mobx-react'; +import { EntityTags, ExtraInfo } from 'Models'; +import React, { useEffect, useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useHistory, useParams } from 'react-router-dom'; +import { getLineageByFQN } from 'rest/lineageAPI'; +import { addLineage, deleteLineageEdge } from 'rest/miscAPI'; +import { + addContainerFollower, + getContainerByName, + patchContainerDetails, + removeContainerFollower, + restoreContainer, +} from 'rest/objectStoreAPI'; +import { + getCurrentUserId, + getEntityMissingError, + getEntityName, + getEntityPlaceHolder, + getOwnerValue, + refreshPage, +} from 'utils/CommonUtils'; +import { getContainerDetailPath } from 'utils/ContainerDetailUtils'; +import { getEntityLineage } from 'utils/EntityUtils'; +import { DEFAULT_ENTITY_PERMISSION } from 'utils/PermissionsUtils'; +import { getLineageViewPath } from 'utils/RouterUtils'; +import { serviceTypeLogo } from 'utils/ServiceUtils'; +import { getTagsWithoutTier, getTierTags } from 'utils/TableUtils'; +import { showErrorToast, showSuccessToast } from 'utils/ToastUtils'; + +enum CONTAINER_DETAILS_TABS { + SCHEME = 'schema', + CHILDREN = 'children', + Lineage = 'lineage', + CUSTOM_PROPERTIES = 'custom-properties', +} + +const ContainerPage = () => { + const history = useHistory(); + const { t } = useTranslation(); + const { getEntityPermissionByFqn } = usePermissionProvider(); + const { containerName, tab = CONTAINER_DETAILS_TABS.SCHEME } = + useParams<{ containerName: string; tab: CONTAINER_DETAILS_TABS }>(); + + // Local states + const [isLoading, setIsLoading] = useState(false); + const [isChildrenLoading, setIsChildrenLoading] = useState(false); + const [hasError, setHasError] = useState(false); + const [isEditDescription, setIsEditDescription] = useState(false); + const [isLineageLoading, setIsLineageLoading] = useState(false); + + const [parentContainers, setParentContainers] = useState([]); + const [containerData, setContainerData] = useState(); + const [containerChildrenData, setContainerChildrenData] = useState< + Container['children'] + >([]); + const [containerPermissions, setContainerPermissions] = + useState(DEFAULT_ENTITY_PERMISSION); + const [entityLineage, setEntityLineage] = useState( + {} as EntityLineage + ); + const [leafNodes, setLeafNodes] = useState({} as LeafNodes); + const [isNodeLoading, setNodeLoading] = useState({ + id: undefined, + state: false, + }); + + // data fetching methods + const fetchContainerParent = async ( + parentName: string, + newContainer = false + ) => { + try { + const response = await getContainerByName(parentName, 'parent'); + setParentContainers((prev) => + newContainer ? [response] : [response, ...prev] + ); + if (response.parent && response.parent.fullyQualifiedName) { + await fetchContainerParent(response.parent.fullyQualifiedName); + } + } catch (error) { + showErrorToast(error as AxiosError, t('server.unexpected-response')); + } + }; + + const fetchContainerDetail = async (containerFQN: string) => { + setIsLoading(true); + try { + const response = await getContainerByName( + containerFQN, + 'parent,dataModel,owner,tags,followers,extension' + ); + setContainerData(response); + if (response.parent && response.parent.fullyQualifiedName) { + await fetchContainerParent(response.parent.fullyQualifiedName, true); + } + } catch (error) { + showErrorToast(error as AxiosError); + setHasError(true); + } finally { + setIsLoading(false); + } + }; + + const fetchContainerChildren = async (containerFQN: string) => { + setIsChildrenLoading(true); + try { + const { children } = await getContainerByName(containerFQN, 'children'); + setContainerChildrenData(children); + } catch (error) { + showErrorToast(error as AxiosError); + } finally { + setIsChildrenLoading(false); + } + }; + + const fetchLineageData = async (containerFQN: string) => { + setIsLineageLoading(true); + try { + const response = await getLineageByFQN( + containerFQN, + EntityType.CONTAINER + ); + + setEntityLineage(response); + } catch (error) { + showErrorToast(error as AxiosError); + } finally { + setIsLineageLoading(false); + } + }; + + const fetchResourcePermission = async (containerFQN: string) => { + setIsLoading(true); + try { + const entityPermission = await getEntityPermissionByFqn( + ResourceEntity.CONTAINER, + containerFQN + ); + setContainerPermissions(entityPermission); + } catch (error) { + showErrorToast( + t('server.fetch-entity-permissions-error', { + entity: t('label.asset-lowercase'), + }) + ); + } finally { + setIsLoading(false); + } + }; + + const { + hasViewPermission, + hasEditDescriptionPermission, + hasEditOwnerPermission, + hasEditTagsPermission, + hasEditTierPermission, + hasEditCustomFieldsPermission, + hasEditLineagePermission, + } = useMemo(() => { + return { + hasViewPermission: + containerPermissions.ViewAll || containerPermissions.ViewBasic, + hasEditDescriptionPermission: + containerPermissions.EditAll || containerPermissions.EditDescription, + hasEditOwnerPermission: + containerPermissions.EditAll || containerPermissions.EditOwner, + hasEditTagsPermission: + containerPermissions.EditAll || containerPermissions.EditTags, + hasEditTierPermission: + containerPermissions.EditAll || containerPermissions.EditTier, + hasEditCustomFieldsPermission: + containerPermissions.EditAll || containerPermissions.EditCustomFields, + hasEditLineagePermission: + containerPermissions.EditAll || containerPermissions.EditLineage, + }; + }, [containerPermissions]); + + const { + tier, + deleted, + owner, + description, + version, + tags, + entityName, + entityId, + followers, + isUserFollowing, + } = useMemo(() => { + return { + deleted: containerData?.deleted, + owner: containerData?.owner, + description: containerData?.description, + version: containerData?.version, + tier: getTierTags(containerData?.tags ?? []), + tags: getTagsWithoutTier(containerData?.tags ?? []), + entityId: containerData?.id, + entityName: getEntityName(containerData), + isUserFollowing: containerData?.followers?.some( + ({ id }: { id: string }) => id === getCurrentUserId() + ), + followers: containerData?.followers ?? [], + }; + }, [containerData]); + + const extraInfo: Array = [ + { + key: EntityInfo.OWNER, + value: owner && getOwnerValue(owner), + placeholderText: getEntityPlaceHolder( + getEntityName(owner), + owner?.deleted + ), + isLink: true, + openInNewTab: false, + profileName: owner?.type === OwnerType.USER ? owner?.name : undefined, + }, + { + key: EntityInfo.TIER, + value: tier?.tagFQN ? tier.tagFQN.split(FQN_SEPARATOR_CHAR)[1] : '', + }, + ]; + + const breadcrumbTitles = useMemo(() => { + const serviceType = containerData?.serviceType; + const service = containerData?.service; + const serviceName = service?.name; + + const parentContainerItems = parentContainers.map((container) => ({ + name: getEntityName(container), + url: getContainerDetailPath(container.fullyQualifiedName ?? ''), + })); + + return [ + { + name: serviceName || '', + url: serviceName + ? getServiceDetailsPath( + serviceName, + ServiceCategory.OBJECT_STORE_SERVICES + ) + : '', + imgSrc: serviceType ? serviceTypeLogo(serviceType) : undefined, + }, + ...parentContainerItems, + { + name: entityName, + url: '', + activeTitle: true, + }, + ]; + }, [containerData, containerName, entityName, parentContainers]); + + // get current user details + const currentUser = useMemo( + () => AppState.getCurrentUserDetails(), + [AppState.userDetails, AppState.nonSecureUserDetails] + ); + + const handleTabChange = (tabValue: string) => { + if (tabValue !== tab) { + history.push({ + pathname: getContainerDetailPath(containerName, tabValue), + }); + } + }; + + const handleUpdateContainerData = (updatedData: Container) => { + const jsonPatch = compare(omitBy(containerData, isUndefined), updatedData); + + return patchContainerDetails(containerData?.id ?? '', jsonPatch); + }; + + const handleUpdateDescription = async (updatedDescription: string) => { + try { + const { description: newDescription, version } = + await handleUpdateContainerData({ + ...(containerData as Container), + description: updatedDescription, + }); + + setContainerData((prev) => ({ + ...(prev as Container), + description: newDescription, + version, + })); + } catch (error) { + showErrorToast(error as AxiosError); + } + }; + + const handleFollowContainer = async () => { + const followerId = currentUser?.id ?? ''; + const containerId = containerData?.id ?? ''; + try { + if (isUserFollowing) { + const response = await removeContainerFollower(containerId, followerId); + const { oldValue } = response.changeDescription.fieldsDeleted[0]; + + setContainerData((prev) => ({ + ...(prev as Container), + followers: (containerData?.followers || []).filter( + (follower) => follower.id !== oldValue[0].id + ), + })); + } else { + const response = await addContainerFollower(containerId, followerId); + const { newValue } = response.changeDescription.fieldsAdded[0]; + + setContainerData((prev) => ({ + ...(prev as Container), + followers: [...(containerData?.followers ?? []), ...newValue], + })); + } + } catch (error) { + showErrorToast(error as AxiosError); + } + }; + + const handleRemoveOwner = async () => { + try { + const { owner: newOwner, version } = await handleUpdateContainerData({ + ...(containerData as Container), + owner: undefined, + }); + + setContainerData((prev) => ({ + ...(prev as Container), + owner: newOwner, + version, + })); + } catch (error) { + showErrorToast(error as AxiosError); + } + }; + + const handleRemoveTier = async () => { + try { + const { tags: newTags, version } = await handleUpdateContainerData({ + ...(containerData as Container), + tags: getTagsWithoutTier(containerData?.tags ?? []), + }); + + setContainerData((prev) => ({ + ...(prev as Container), + tags: newTags, + version, + })); + } catch (error) { + showErrorToast(error as AxiosError); + } + }; + + const handleUpdateOwner = async (updatedOwner?: Container['owner']) => { + try { + if (updatedOwner) { + const { owner: newOwner, version } = await handleUpdateContainerData({ + ...(containerData as Container), + owner: updatedOwner ?? containerData?.owner, + }); + + setContainerData((prev) => ({ + ...(prev as Container), + owner: newOwner, + version, + })); + } + } catch (error) { + showErrorToast(error as AxiosError); + } + }; + + const handleUpdateTier = async (updatedTier?: string) => { + try { + if (updatedTier) { + const { tags: newTags, version } = await handleUpdateContainerData({ + ...(containerData as Container), + tags: [ + ...(containerData?.tags ?? []), + { + tagFQN: updatedTier, + labelType: LabelType.Manual, + state: State.Confirmed, + source: TagSource.Classification, + }, + ], + }); + + setContainerData((prev) => ({ + ...(prev as Container), + tags: newTags, + version, + })); + } + } catch (error) { + showErrorToast(error as AxiosError); + } + }; + + const handleUpdateTags = async (selectedTags: Array = []) => { + try { + const { tags: newTags, version } = await handleUpdateContainerData({ + ...(containerData as Container), + tags: [...(tier ? [tier] : []), ...selectedTags], + }); + + setContainerData((prev) => ({ + ...(prev as Container), + tags: newTags, + version, + })); + } catch (error) { + showErrorToast(error as AxiosError); + } + }; + + const handleRestoreContainer = async () => { + try { + await restoreContainer(containerData?.id ?? ''); + showSuccessToast( + t('message.restore-entities-success', { + entity: t('label.container'), + }), + 2000 + ); + refreshPage(); + } catch (error) { + showErrorToast( + error as AxiosError, + t('message.restore-entities-error', { + entity: t('label.container'), + }) + ); + } + }; + + // Lineage handlers + const handleAddLineage = async (edge: Edge) => { + try { + await addLineage(edge); + } catch (error) { + showErrorToast(error as AxiosError); + } + }; + + const handleRemoveLineage = async (data: EdgeData) => { + try { + await deleteLineageEdge( + data.fromEntity, + data.fromId, + data.toEntity, + data.toId + ); + } catch (error) { + showErrorToast(error as AxiosError); + } + }; + + const handleSetLeafNode = (val: EntityLineage, pos: LineagePos) => { + if (pos === 'to' && val.downstreamEdges?.length === 0) { + setLeafNodes((prev) => ({ + ...prev, + downStreamNode: [...(prev.downStreamNode ?? []), val.entity.id], + })); + } + if (pos === 'from' && val.upstreamEdges?.length === 0) { + setLeafNodes((prev) => ({ + ...prev, + upStreamNode: [...(prev.upStreamNode ?? []), val.entity.id], + })); + } + }; + + const handleLoadLineageNode = async ( + node: EntityReference, + pos: LineagePos + ) => { + setNodeLoading({ id: node.id, state: true }); + + try { + const response = await getLineageByFQN( + node.fullyQualifiedName ?? '', + node.type + ); + handleSetLeafNode(response, pos); + setEntityLineage(getEntityLineage(entityLineage, response, pos)); + setTimeout(() => { + setNodeLoading((prev) => ({ ...prev, state: false })); + }, 500); + } catch (error) { + showErrorToast(error as AxiosError); + } + }; + + const handleFullScreenClick = () => + history.push(getLineageViewPath(EntityType.CONTAINER, containerName)); + + const handleExtensionUpdate = async (updatedContainer: Container) => { + try { + const response = await handleUpdateContainerData(updatedContainer); + setContainerData(response); + } catch (error) { + showErrorToast(error as AxiosError); + } + }; + + const handleUpdateDataModel = async ( + updatedDataModel: Container['dataModel'] + ) => { + try { + const { dataModel: newDataModel, version } = + await handleUpdateContainerData({ + ...(containerData as Container), + dataModel: updatedDataModel, + }); + + setContainerData((prev) => ({ + ...(prev as Container), + dataModel: newDataModel, + version, + })); + } catch (error) { + showErrorToast(error as AxiosError); + } + }; + + // Effects + useEffect(() => { + if (hasViewPermission) { + fetchContainerDetail(containerName); + } + }, [containerName, containerPermissions]); + + useEffect(() => { + fetchResourcePermission(containerName); + // reset parent containers list on containername change + setParentContainers([]); + }, [containerName]); + + useEffect(() => { + if (tab === CONTAINER_DETAILS_TABS.Lineage) { + fetchLineageData(containerName); + } + if (tab === CONTAINER_DETAILS_TABS.CHILDREN) { + fetchContainerChildren(containerName); + } + }, [tab, containerName]); + + // Rendering + if (isLoading) { + return ; + } + + if (hasError) { + return ( + + {getEntityMissingError(t('label.container'), containerName)} + + ); + } + + if (!hasViewPermission && !isLoading) { + return {NO_PERMISSION_TO_VIEW}; + } + + return ( + +
+ + + + {t('label.schema')} + + }> + +
+ setIsEditDescription(false)} + onDescriptionEdit={() => setIsEditDescription(true)} + onDescriptionUpdate={handleUpdateDescription} + /> + + + + + + + + {t('label.children')} + + }> + + + {isChildrenLoading ? ( + + ) : ( + + )} + + + + + {t('label.lineage')} + + }> + + setEntityLineage(lineage)} + entityType={EntityType.CONTAINER} + hasEditAccess={hasEditLineagePermission} + isLoading={isLineageLoading} + isNodeLoading={isNodeLoading} + lineageLeafNodes={leafNodes} + loadNodeHandler={handleLoadLineageNode} + removeLineageHandler={handleRemoveLineage} + onFullScreenClick={handleFullScreenClick} + /> + + + + {t('label.custom-property-plural')} + + }> + + + + + + + + ); +}; + +export default observer(ContainerPage); diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/CustomPropertiesPageV1/CustomPropertiesPageV1.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/CustomPropertiesPageV1/CustomPropertiesPageV1.tsx index e4b2fe5e40b..6276e2682f5 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/CustomPropertiesPageV1/CustomPropertiesPageV1.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/CustomPropertiesPageV1/CustomPropertiesPageV1.tsx @@ -164,6 +164,9 @@ const CustomEntityDetailV1 = () => { case ENTITY_PATH.mlmodels: return PAGE_HEADERS.ML_MODELS_CUSTOM_ATTRIBUTES; + + case ENTITY_PATH.containers: + return PAGE_HEADERS.CONTAINER_CUSTOM_ATTRIBUTES; default: return PAGE_HEADERS.TABLES_CUSTOM_ATTRIBUTES; } diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/service/index.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/service/index.tsx index b98dd4264de..50dd65102f7 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/service/index.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/service/index.tsx @@ -32,6 +32,7 @@ import { usePermissionProvider } from 'components/PermissionProvider/PermissionP import { OperationPermission } from 'components/PermissionProvider/PermissionProvider.interface'; import ServiceConnectionDetails from 'components/ServiceConnectionDetails/ServiceConnectionDetails.component'; import TagsViewer from 'components/Tag/TagsViewer/tags-viewer'; +import { EntityType } from 'enums/entity.enum'; import { Container } from 'generated/entity/data/container'; import { isEmpty, isNil, isUndefined, startCase, toLower } from 'lodash'; import { ExtraInfo, ServicesUpdateRequest, ServiceTypes } from 'Models'; @@ -488,10 +489,10 @@ const ServicePage: FunctionComponent = () => { setData(response.data); setPaging(response.paging); - setIsLoading(false); } catch (error) { setData([]); setPaging(pagingObject); + } finally { setIsLoading(false); } }; @@ -548,11 +549,7 @@ const ServicePage: FunctionComponent = () => { return getEntityLink(SearchIndex.MLMODEL, fqn); case ServiceCategory.OBJECT_STORE_SERVICES: - /** - * Update this when containers details page is ready - */ - - return ''; + return getEntityLink(EntityType.CONTAINER, fqn); case ServiceCategory.DATABASE_SERVICES: default: @@ -632,7 +629,7 @@ const ServicePage: FunctionComponent = () => { case ServiceCategory.OBJECT_STORE_SERVICES: { const container = data as Container; - return container.tags && container.tags?.length > 0 ? ( + return container.tags && container.tags.length > 0 ? ( { + const response = await APIClient.get( + `containers/name/${name}?fields=${fields}` + ); + + return response.data; +}; + +export const patchContainerDetails = async (id: string, data: Operation[]) => { + const response = await APIClient.patch>( + `/containers/${id}`, + data, + configOptionsForPatch + ); + + return response.data; +}; + +export const addContainerFollower = async (id: string, userId: string) => { + const response = await APIClient.put< + string, + AxiosResponse<{ + changeDescription: { fieldsAdded: { newValue: EntityReference[] }[] }; + }> + >(`/containers/${id}/followers`, userId, configOptions); + + return response.data; +}; + +export const restoreContainer = async (id: string) => { + const response = await APIClient.put< + RestoreRequestType, + AxiosResponse + >('/containers/restore', { id }); + + return response.data; +}; + +export const removeContainerFollower = async (id: string, userId: string) => { + const response = await APIClient.delete< + string, + AxiosResponse<{ + changeDescription: { fieldsDeleted: { oldValue: EntityReference[] }[] }; + }> + >(`/containers/${id}/followers/${userId}`, configOptions); + + return response.data; +}; diff --git a/openmetadata-ui/src/main/resources/ui/src/styles/components/menu.less b/openmetadata-ui/src/main/resources/ui/src/styles/components/menu.less index c84db0e57ae..84a99ca1f6c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/styles/components/menu.less +++ b/openmetadata-ui/src/main/resources/ui/src/styles/components/menu.less @@ -59,6 +59,16 @@ .ant-menu-item-selected { .ant-menu-title-content { font-weight: 600; + .ant-badge { + color: @primary-color; + } + } + } + .ant-menu-item-active { + .ant-menu-title-content { + .ant-badge { + color: @primary-color; + } } } diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/CommonUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/CommonUtils.tsx index 7ef671e931d..72cfbf744fd 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/CommonUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/CommonUtils.tsx @@ -23,6 +23,7 @@ import { } from 'components/common/CronEditor/CronEditor.constant'; import ErrorPlaceHolder from 'components/common/error-with-placeholder/ErrorPlaceHolder'; import Loader from 'components/Loader/Loader'; +import { Container } from 'generated/entity/data/container'; import { t } from 'i18next'; import { capitalize, @@ -71,7 +72,7 @@ import { Dashboard } from '../generated/entity/data/dashboard'; import { Database } from '../generated/entity/data/database'; import { GlossaryTerm } from '../generated/entity/data/glossaryTerm'; import { Pipeline } from '../generated/entity/data/pipeline'; -import { Table } from '../generated/entity/data/table'; +import { Column, Table } from '../generated/entity/data/table'; import { Topic } from '../generated/entity/data/topic'; import { Webhook } from '../generated/entity/events/webhook'; import { ThreadTaskStatus, ThreadType } from '../generated/entity/feed/thread'; @@ -572,6 +573,8 @@ export const getEntityName = ( | Kpi | Classification | Field + | Container + | Column ) => { return entity?.displayName || entity?.name || ''; }; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/ContainerDetailUtils.test.ts b/openmetadata-ui/src/main/resources/ui/src/utils/ContainerDetailUtils.test.ts new file mode 100644 index 00000000000..78a9cc470a5 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/utils/ContainerDetailUtils.test.ts @@ -0,0 +1,202 @@ +/* + * Copyright 2023 Collate. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { Column, DataType } from 'generated/entity/data/container'; +import { + getContainerDetailPath, + updateContainerColumnDescription, + updateContainerColumnTags, +} from './ContainerDetailUtils'; + +const mockTagOptions = [ + { + fqn: 'PII.Sensitive', + source: 'Classification', + }, + { + fqn: 'PersonalData.Personal', + source: 'Classification', + }, +]; + +const mockTags = [ + { + tagFQN: 'PII.Sensitive', + source: 'Classification', + labelType: 'Manual', + state: 'Confirmed', + }, + { + tagFQN: 'PersonalData.Personal', + source: 'Classification', + labelType: 'Manual', + state: 'Confirmed', + }, +]; + +const nestedColumn = { + name: 'Order', + displayName: 'Order', + dataType: DataType.Record, + description: 'All the order events on our online store', + children: [ + { + name: 'order_id', + dataType: DataType.Int, + description: 'order_id', + }, + { + name: 'api_client_id', + dataType: DataType.Int, + description: 'api_client_id', + }, + ], +}; + +const singleColumn = { + name: 'id', + dataType: DataType.String, + fullyQualifiedName: 'sample_kafka.customer_events.id', +}; + +const updatedNestedColumn: Column = { + name: 'Order', + displayName: 'Order', + dataType: DataType.Record, + description: 'All the order events on our online store', + children: [ + { + name: 'order_id', + dataType: DataType.Int, + description: 'order_id', + }, + { + name: 'api_client_id', + dataType: DataType.Int, + description: 'updated description', + }, + ], +}; + +const updatedSingleColumn = { + name: 'id', + dataType: DataType.String, + fullyQualifiedName: 'sample_kafka.customer_events.id', + description: 'updated description', +}; + +const nestedColumnWithTags = { + name: 'Order', + displayName: 'Order', + dataType: DataType.Record, + description: 'All the order events on our online store', + children: [ + { + name: 'order_id', + dataType: DataType.Int, + description: 'order_id', + tags: [], + }, + { + name: 'api_client_id', + dataType: DataType.Int, + description: 'api_client_id', + tags: [], + }, + ], +}; + +const updatedNestedColumnWithTags: Column = { + name: 'Order', + displayName: 'Order', + dataType: DataType.Record, + description: 'All the order events on our online store', + children: [ + { + name: 'order_id', + dataType: DataType.Int, + description: 'order_id', + tags: mockTags as Column['tags'], + }, + { + name: 'api_client_id', + dataType: DataType.Int, + description: 'api_client_id', + tags: [], + }, + ], +}; + +describe('getContainerDetailPath', () => { + it('returns the correct path without tab', () => { + const containerFQN = 'my-container'; + const path = getContainerDetailPath(containerFQN); + + expect(path).toEqual(`/container/${containerFQN}`); + }); + + it('returns the correct path with tab', () => { + const containerFQN = 'my-container'; + const tab = 'my-tab'; + const path = getContainerDetailPath(containerFQN, tab); + + expect(path).toEqual(`/container/${containerFQN}/${tab}`); + }); + + it('updateContainerColumnDescription method should update the column', () => { + const containerColumns = [singleColumn, nestedColumn]; + + // updated the single column + updateContainerColumnDescription( + containerColumns, + 'id', + 'updated description' + ); + + // updated the nested column + updateContainerColumnDescription( + containerColumns, + 'api_client_id', + 'updated description' + ); + + const updatedContainerColumns = [updatedSingleColumn, updatedNestedColumn]; + + expect(containerColumns).toEqual(updatedContainerColumns); + }); + + it('updateContainerColumnTags method should update the column', () => { + const containerColumns = [ + { ...singleColumn, tags: [], description: 'updated description' }, + ]; + + // updated the single column + updateContainerColumnTags(containerColumns, 'id', mockTagOptions); + + const updatedContainerColumns = [ + { ...updatedSingleColumn, tags: mockTags }, + ]; + + expect(containerColumns).toEqual(updatedContainerColumns); + }); + + it('updateContainerColumnTags method should update the nested column', () => { + const containerColumns = [nestedColumnWithTags]; + + // updated the single column + updateContainerColumnTags(containerColumns, 'order_id', mockTagOptions); + + const updatedContainerColumns = [updatedNestedColumnWithTags]; + + expect(containerColumns).toEqual(updatedContainerColumns); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/ContainerDetailUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/ContainerDetailUtils.ts new file mode 100644 index 00000000000..9f045423854 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/utils/ContainerDetailUtils.ts @@ -0,0 +1,112 @@ +/* + * Copyright 2023 Collate. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { + PLACEHOLDER_CONTAINER_NAME, + PLACEHOLDER_ROUTE_TAB, + ROUTES, +} from 'constants/constants'; +import { Column, ContainerDataModel } from 'generated/entity/data/container'; +import { LabelType, State, TagLabel } from 'generated/type/tagLabel'; +import { isEmpty } from 'lodash'; +import { EntityTags, TagOption } from 'Models'; + +export const getContainerDetailPath = (containerFQN: string, tab?: string) => { + let path = tab ? ROUTES.CONTAINER_DETAILS_WITH_TAB : ROUTES.CONTAINER_DETAILS; + path = path.replace(PLACEHOLDER_CONTAINER_NAME, containerFQN); + + if (tab) { + path = path.replace(PLACEHOLDER_ROUTE_TAB, tab); + } + + return path; +}; + +const getUpdatedContainerColumnTags = ( + containerColumn: Column, + newContainerColumnTags: TagOption[] = [] +) => { + const newTagsFqnList = newContainerColumnTags.map((newTag) => newTag.fqn); + + const prevTags = containerColumn?.tags?.filter((tag) => + newTagsFqnList.includes(tag.tagFQN) + ); + + const prevTagsFqnList = prevTags?.map((prevTag) => prevTag.tagFQN); + + const newTags: EntityTags[] = newContainerColumnTags.reduce((prev, curr) => { + const isExistingTag = prevTagsFqnList?.includes(curr.fqn); + + return isExistingTag + ? prev + : [ + ...prev, + { + labelType: LabelType.Manual, + state: State.Confirmed, + source: curr.source, + tagFQN: curr.fqn, + }, + ]; + }, [] as EntityTags[]); + + return [...(prevTags as TagLabel[]), ...newTags]; +}; + +export const updateContainerColumnTags = ( + containerColumns: ContainerDataModel['columns'] = [], + changedColumnName: string, + newColumnTags: TagOption[] = [] +) => { + containerColumns.forEach((containerColumn) => { + if (containerColumn.name === changedColumnName) { + containerColumn.tags = getUpdatedContainerColumnTags( + containerColumn, + newColumnTags + ); + } else { + const hasChildren = !isEmpty(containerColumn.children); + + // stop condition + if (hasChildren) { + updateContainerColumnTags( + containerColumn.children, + changedColumnName, + newColumnTags + ); + } + } + }); +}; + +export const updateContainerColumnDescription = ( + containerColumns: ContainerDataModel['columns'] = [], + changedColumnName: string, + description: string +) => { + containerColumns.forEach((containerColumn) => { + if (containerColumn.name === changedColumnName) { + containerColumn.description = description; + } else { + const hasChildren = !isEmpty(containerColumn.children); + + // stop condition + if (hasChildren) { + updateContainerColumnDescription( + containerColumn.children, + changedColumnName, + description + ); + } + } + }); +}; diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/GlobalSettingsUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/GlobalSettingsUtils.tsx index 6208e68cd2a..a1db217801e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/GlobalSettingsUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/GlobalSettingsUtils.tsx @@ -13,6 +13,7 @@ import { Badge } from 'antd'; import { ItemType } from 'antd/lib/menu/hooks/useItems'; +import classNames from 'classnames'; import { ResourceEntity, UIPermission, @@ -163,6 +164,7 @@ export const getGlobalSettingsMenuWithPermission = ( permissions ), icon: , + isBeta: Boolean, }, ], }, @@ -225,6 +227,14 @@ export const getGlobalSettingsMenuWithPermission = ( ), icon: , }, + { + label: i18next.t('label.container-plural'), + isProtected: userPermissions.hasViewPermissions( + ResourceEntity.TYPE, + permissions + ), + icon: , + }, ], }, { @@ -255,30 +265,42 @@ export const getGlobalSettingsMenuWithPermission = ( ]; }; -export const getGlobalSettingMenuItem = ( - label: string, - key: string, - category?: string, - icon?: React.ReactNode, +export const getGlobalSettingMenuItem = (args: { + label: string; + key: string; + category?: string; + icon?: React.ReactNode; children?: { label: string; isProtected: boolean; icon: React.ReactNode; - }[], - type?: string, - isBeta?: boolean -): { + isBeta?: boolean; + }[]; + type?: string; + isBeta?: boolean; + isChildren?: boolean; +}): { key: string; icon: React.ReactNode; children: ItemType[] | undefined; label: ReactNode; type: string | undefined; } => { + const { children, label, key, icon, category, isBeta, type, isChildren } = + args; + const subItems = children ? children .filter((menu) => menu.isProtected) - .map(({ label, icon }) => { - return getGlobalSettingMenuItem(label, camelCase(label), key, icon); + .map(({ label, icon, isBeta: isChildBeta }) => { + return getGlobalSettingMenuItem({ + label, + key: camelCase(label), + category: key, + icon, + isBeta: isChildBeta, + isChildren: true, + }); }) : undefined; @@ -288,7 +310,7 @@ export const getGlobalSettingMenuItem = ( children: subItems, label: isBeta ? ( { case MetadataServiceType.OpenMetadata: return LOGO; + case ObjectStoreServiceType.Azure: + return MS_AZURE; + + case ObjectStoreServiceType.S3: + return AMAZON_S3; + + case ObjectStoreServiceType.Gcs: + return GCS; + default: { let logo; if (serviceTypes.messagingServices.includes(type)) { diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/TableUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/TableUtils.tsx index 2c83237332b..3abc429b0c1 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/TableUtils.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/utils/TableUtils.tsx @@ -63,6 +63,7 @@ import { getTableFQNFromColumnFQN, sortTagsCaseInsensitive, } from './CommonUtils'; +import { getContainerDetailPath } from './ContainerDetailUtils'; import { getGlossaryPath, getSettingPath } from './RouterUtils'; import { ordinalize } from './StringsUtils'; @@ -245,6 +246,9 @@ export const getEntityLink = ( case SearchIndex.MLMODEL: return getMlModelPath(fullyQualifiedName); + case EntityType.CONTAINER: + return getContainerDetailPath(fullyQualifiedName); + case SearchIndex.TABLE: case EntityType.TABLE: default: