From 786be3bedf1f95ec04c8fe29e4883cfcf9da0ba1 Mon Sep 17 00:00:00 2001 From: Sachin Chaurasiya Date: Mon, 17 Oct 2022 18:47:17 +0530 Subject: [PATCH] Feat (#7561) Announcements are not supported for MLModel from UI (#8109) * Feat (#7561) Announcements are not supported for MLModel from UI * Fix unit test * Use Async/Await pattern * Add conversation support for mlModel * Add task support for mlModel * Fix task page loading issue * Fix unit test * Add End to end test for entity announcement * Fix Add CreateAnnouncement function issue * Add End to End Test for entity task * Fix End to End Test for Announcement and Tasks * Revert "Fix End to End Test for Announcement and Tasks" This reverts commit 48cbc0b6158b352b9e19e8290ff52a47849bb648. * Fix Description unit test * Fix cypress test * Fix cypress test * Fix entity task cypress test * Remove EntityTask Spec * Addressing review comment * Addressing review comment * Addressing review comment --- .../ui/cypress/constants/constants.js | 2 + .../e2e/Flow/EntityAnnouncement.spec.js | 91 ++++ .../AnnouncementThreads.tsx | 4 +- .../MlModelDetail.component.test.tsx | 27 +- .../MlModelDetail/MlModelDetail.component.tsx | 165 ++++-- .../MlModelDetail/MlModelDetail.interface.ts | 72 +++ .../common/AssigneeList/AssigneeList.tsx | 1 + .../common/description/Description.test.tsx | 2 +- .../common/description/Description.tsx | 6 +- .../common/entityPageInfo/EntityPageInfo.tsx | 8 +- .../MlModelPage/MlModelPage.component.tsx | 174 +++++- .../RequestTagPage/RequestTagPage.tsx | 2 +- .../TaskDetailPage/TaskDetailPage.tsx | 502 +++++++++--------- .../pages/TasksPage/TasksPage.interface.ts | 3 +- .../src/main/resources/ui/src/setupTests.js | 8 + .../src/main/resources/ui/src/styles/app.less | 6 + .../ui/src/utils/AnnouncementsUtils.ts | 1 + .../ui/src/utils/MlModelDetailsUtils.ts | 15 +- .../main/resources/ui/src/utils/TasksUtils.ts | 15 + 19 files changed, 797 insertions(+), 307 deletions(-) create mode 100644 openmetadata-ui/src/main/resources/ui/cypress/e2e/Flow/EntityAnnouncement.spec.js create mode 100644 openmetadata-ui/src/main/resources/ui/src/components/MlModelDetail/MlModelDetail.interface.ts diff --git a/openmetadata-ui/src/main/resources/ui/cypress/constants/constants.js b/openmetadata-ui/src/main/resources/ui/cypress/constants/constants.js index 6229c5a909e..aae862514ff 100644 --- a/openmetadata-ui/src/main/resources/ui/cypress/constants/constants.js +++ b/openmetadata-ui/src/main/resources/ui/cypress/constants/constants.js @@ -263,6 +263,8 @@ export const LOGIN = { password: 'admin', }; +export const ANNOUNCEMENT_ENTITIES = [SEARCH_ENTITY_TABLE.table_1, SEARCH_ENTITY_TOPIC.topic_1, SEARCH_ENTITY_DASHBOARD.dashboard_1, SEARCH_ENTITY_PIPELINE.pipeline_1] + export const HTTP_CONFIG_SOURCE = { DBT_CATALOG_HTTP_PATH: 'https://raw.githubusercontent.com/OnkarVO7/dbt_git_test/master/catalog.json', diff --git a/openmetadata-ui/src/main/resources/ui/cypress/e2e/Flow/EntityAnnouncement.spec.js b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Flow/EntityAnnouncement.spec.js new file mode 100644 index 00000000000..bfbefb4e03e --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/cypress/e2e/Flow/EntityAnnouncement.spec.js @@ -0,0 +1,91 @@ +/* + * Copyright 2021 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 { getCurrentLocaleDate, getFutureLocaleDateFromCurrentDate } from "../../../src/utils/TimeUtils"; +import { descriptionBox, login, visitEntityDetailsPage } from "../../common/common"; +import { ANNOUNCEMENT_ENTITIES, LOGIN } from "../../constants/constants"; + + + +describe("Entity Announcement", () => { + beforeEach(() => { + login(LOGIN.username, LOGIN.password); + cy.goToHomePage(); + }); + + const createAnnouncement = (title, startDate, endDate, description) => { + cy.get('[data-testid="add-announcement"]').should('be.visible').click(); + cy.get('.ant-modal-header') + .should('be.visible') + .contains('Make an announcement'); + cy.get('.ant-modal-body').should('be.visible'); + + cy.get('#title').should('be.visible').type(title); + cy.get('#startDate').should('be.visible').type(startDate); + cy.get('#endtDate').should('be.visible').type(endDate); + cy.get(descriptionBox).type(description); + + cy.get('.ant-modal-footer > .ant-btn-primary') + .should('be.visible') + .contains('Submit') + .scrollIntoView() + .click(); + } + + const addAnnouncement = (value) => { + const startDate = getCurrentLocaleDate(); + const endDate = getFutureLocaleDateFromCurrentDate(5); + visitEntityDetailsPage(value.term, value.serviceName, value.entity); + + cy.get('[data-testid="manage-button"]').should('be.visible').click(); + cy.get('[data-testid="announcement-button"]').should('be.visible').click(); + cy.get('[data-testid="announcement-error"]') + .should('be.visible') + .contains('No Announcements, Click on add announcement to add one.'); + + // Create Active Announcement + createAnnouncement("Announcement Title", startDate, endDate, "Announcement Description") + + // wait time for success toast message + cy.wait(5000); + + // Create InActive Announcement + const InActiveStartDate = getFutureLocaleDateFromCurrentDate(6); + const InActiveEndDate = getFutureLocaleDateFromCurrentDate(11); + + createAnnouncement("InActive Announcement Title",InActiveStartDate,InActiveEndDate,"InActive Announcement Description") + + // wait time for success toast message + cy.wait(5000); + + // check for inActive-announcement + cy.get('[data-testid="inActive-announcements"]').should('be.visible'); + + // close announcement drawer + cy.get('.anticon > svg').should('be.visible').click(); + + // reload page to get the active announcement card + cy.reload(); + + // check for announcement card on entity page + cy.get('[data-testid="announcement-card"]').should('be.visible'); + }; + + + + ANNOUNCEMENT_ENTITIES.forEach((entity) => { + it(`Add announcement and verify the active announcement for ${entity.entity}`, () => { + addAnnouncement(entity) + }) + }) +}) \ No newline at end of file diff --git a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityThreadPanel/AnnouncementThreads.tsx b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityThreadPanel/AnnouncementThreads.tsx index 0c5042cf6c6..81a5a0e0900 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityThreadPanel/AnnouncementThreads.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/ActivityFeed/ActivityThreadPanel/AnnouncementThreads.tsx @@ -171,7 +171,9 @@ const AnnouncementThreads: FC = ({ {getAnnouncements(activeAnnouncements)} {Boolean(inActiveAnnouncements.length) && ( <> - + Inactive Announcements diff --git a/openmetadata-ui/src/main/resources/ui/src/components/MlModelDetail/MlModelDetail.component.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/MlModelDetail/MlModelDetail.component.test.tsx index d2ae6415aae..23b63bed87c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/MlModelDetail/MlModelDetail.component.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/MlModelDetail/MlModelDetail.component.test.tsx @@ -16,6 +16,7 @@ import { LeafNodes } from 'Models'; import React from 'react'; import { MemoryRouter } from 'react-router-dom'; import { Mlmodel } from '../../generated/entity/data/mlmodel'; +import { Paging } from '../../generated/type/paging'; import MlModelDetailComponent from './MlModelDetail.component'; const mockData = { @@ -153,6 +154,18 @@ const mockProp = { isNodeLoading: { id: undefined, state: false }, }, onExtensionUpdate: jest.fn(), + entityThread: [], + isEntityThreadLoading: false, + paging: {} as Paging, + feedCount: 2, + fetchFeedHandler: jest.fn(), + postFeedHandler: jest.fn(), + deletePostHandler: jest.fn(), + + updateThreadHandler: jest.fn(), + entityFieldThreadCount: [], + entityFieldTaskCount: [], + createThread: jest.fn(), }; jest.mock('../common/description/Description', () => { @@ -179,6 +192,14 @@ jest.mock('../common/TabsPane/TabsPane', () => { return jest.fn().mockReturnValue(

Tabs

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

ActivityFeedList

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

ActivityThreadPanel

); +}); + jest.mock('../../utils/CommonUtils', () => { return { getEntityName: jest.fn().mockReturnValue('entityName'), @@ -224,7 +245,7 @@ describe('Test MlModel entity detail component', () => { it('Should render hyper parameter and ml store table for details tab', async () => { const { container } = render( - , + , { wrapper: MemoryRouter, } @@ -245,7 +266,7 @@ describe('Test MlModel entity detail component', () => { it('Should render lineage tab', async () => { const { container } = render( - , + , { wrapper: MemoryRouter, } @@ -258,7 +279,7 @@ describe('Test MlModel entity detail component', () => { it('Check if active tab is custom properties', async () => { const { container } = render( - , + , { wrapper: MemoryRouter, } diff --git a/openmetadata-ui/src/main/resources/ui/src/components/MlModelDetail/MlModelDetail.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/MlModelDetail/MlModelDetail.component.tsx index febb6b242d5..de815209133 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/MlModelDetail/MlModelDetail.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/MlModelDetail/MlModelDetail.component.tsx @@ -11,22 +11,16 @@ * limitations under the License. */ -import { Table } from 'antd'; +import { Col, Row, Table } from 'antd'; import { ColumnsType } from 'antd/lib/table'; import classNames from 'classnames'; import { isUndefined, startCase, uniqueId } from 'lodash'; import { observer } from 'mobx-react'; -import { - EntityTags, - ExtraInfo, - LeafNodes, - LineagePos, - LoadingNodeState, -} from 'Models'; +import { EntityTags, ExtraInfo } from 'Models'; import React, { FC, Fragment, - HTMLAttributes, + RefObject, useCallback, useEffect, useMemo, @@ -38,24 +32,31 @@ import { getDashboardDetailsPath, getServiceDetailsPath, } from '../../constants/constants'; +import { EntityField } from '../../constants/feed.constants'; +import { observerOptions } from '../../constants/Mydata.constants'; import { EntityType } from '../../enums/entity.enum'; import { ServiceCategory } from '../../enums/service.enum'; import { OwnerType } from '../../enums/user.enum'; import { MlHyperParameter } from '../../generated/api/data/createMlModel'; import { Mlmodel } from '../../generated/entity/data/mlmodel'; -import { EntityLineage } from '../../generated/type/entityLineage'; +import { ThreadType } from '../../generated/entity/feed/thread'; import { EntityReference } from '../../generated/type/entityReference'; +import { Paging } from '../../generated/type/paging'; import { LabelType, State, TagLabel } from '../../generated/type/tagLabel'; +import { useInfiniteScroll } from '../../hooks/useInfiniteScroll'; import jsonData from '../../jsons/en'; import { getEntityName, getEntityPlaceHolder, getOwnerValue, } from '../../utils/CommonUtils'; +import { getEntityFieldThreadCounts } from '../../utils/FeedUtils'; import { DEFAULT_ENTITY_PERMISSION } from '../../utils/PermissionsUtils'; import { serviceTypeLogo } from '../../utils/ServiceUtils'; import { getTagsWithoutTier, getTierTags } from '../../utils/TableUtils'; import { showErrorToast } from '../../utils/ToastUtils'; +import ActivityFeedList from '../ActivityFeed/ActivityFeedList/ActivityFeedList'; +import ActivityThreadPanel from '../ActivityFeed/ActivityThreadPanel/ActivityThreadPanel'; import { CustomPropertyTable } from '../common/CustomPropertyTable/CustomPropertyTable'; import { CustomPropertyProps } from '../common/CustomPropertyTable/CustomPropertyTable.interface'; import Description from '../common/description/Description'; @@ -64,34 +65,12 @@ import TabsPane from '../common/TabsPane/TabsPane'; import { TitleBreadcrumbProps } from '../common/title-breadcrumb/title-breadcrumb.interface'; import PageContainer from '../containers/PageContainer'; import EntityLineageComponent from '../EntityLineage/EntityLineage.component'; -import { Edge, EdgeData } from '../EntityLineage/EntityLineage.interface'; +import Loader from '../Loader/Loader'; import { usePermissionProvider } from '../PermissionProvider/PermissionProvider'; import { ResourceEntity } from '../PermissionProvider/PermissionProvider.interface'; +import { MlModelDetailProp } from './MlModelDetail.interface'; import MlModelFeaturesList from './MlModelFeaturesList'; -interface MlModelDetailProp extends HTMLAttributes { - mlModelDetail: Mlmodel; - activeTab: number; - followMlModelHandler: () => void; - unfollowMlModelHandler: () => void; - descriptionUpdateHandler: (updatedMlModel: Mlmodel) => Promise; - setActiveTabHandler: (value: number) => void; - tagUpdateHandler: (updatedMlModel: Mlmodel) => void; - updateMlModelFeatures: (updatedMlModel: Mlmodel) => Promise; - settingsUpdateHandler: (updatedMlModel: Mlmodel) => Promise; - lineageTabData: { - loadNodeHandler: (node: EntityReference, pos: LineagePos) => void; - addLineageHandler: (edge: Edge) => Promise; - removeLineageHandler: (data: EdgeData) => void; - entityLineageHandler: (lineage: EntityLineage) => void; - isLineageLoading?: boolean; - entityLineage: EntityLineage; - lineageLeafNodes: LeafNodes; - isNodeLoading: LoadingNodeState; - }; - onExtensionUpdate: (updatedMlModel: Mlmodel) => Promise; -} - const MlModelDetail: FC = ({ mlModelDetail, activeTab, @@ -104,6 +83,17 @@ const MlModelDetail: FC = ({ updateMlModelFeatures, lineageTabData, onExtensionUpdate, + entityThread, + isEntityThreadLoading, + fetchFeedHandler, + deletePostHandler, + postFeedHandler, + updateThreadHandler, + paging, + feedCount, + createThread, + entityFieldTaskCount, + entityFieldThreadCount, }) => { const [followersCount, setFollowersCount] = useState(0); const [isFollowing, setIsFollowing] = useState(false); @@ -114,6 +104,11 @@ const MlModelDetail: FC = ({ DEFAULT_ENTITY_PERMISSION ); + const [threadType, setThreadType] = useState( + ThreadType.Conversation + ); + const [threadLink, setThreadLink] = useState(''); + const { getEntityPermission } = usePermissionProvider(); const fetchResourcePermission = useCallback(async () => { @@ -130,6 +125,8 @@ const MlModelDetail: FC = ({ } }, [mlModelDetail.id, getEntityPermission, setPipelinePermissions]); + const [elementRef, isInView] = useInfiniteScroll(observerOptions); + useEffect(() => { if (mlModelDetail.id) { fetchResourcePermission(); @@ -232,6 +229,12 @@ const MlModelDetail: FC = ({ isProtected: false, position: 1, }, + { + name: 'Activity Feeds & Tasks', + isProtected: false, + position: 2, + count: feedCount, + }, { name: 'Details', icon: { @@ -241,17 +244,17 @@ const MlModelDetail: FC = ({ selectedName: 'icon-detailscolor', }, isProtected: false, - position: 2, + position: 3, }, { name: 'Lineage', isProtected: false, - position: 3, + position: 4, }, { name: 'Custom Properties', isProtected: false, - position: 4, + position: 5, }, ]; @@ -341,6 +344,17 @@ const MlModelDetail: FC = ({ await updateMlModelFeatures({ ...mlModelDetail, mlFeatures: features }); }; + const handleThreadLinkSelect = (link: string, threadType?: ThreadType) => { + setThreadLink(link); + if (threadType) { + setThreadType(threadType); + } + }; + + const handleThreadPanelClose = () => { + setThreadLink(''); + }; + const getMlHyperParametersColumn: ColumnsType = useMemo( () => [ { @@ -430,6 +444,27 @@ const MlModelDetail: FC = ({ ); }; + const fetchMoreThread = ( + isElementInView: boolean, + pagingObj: Paging, + isLoading: boolean + ) => { + if (isElementInView && pagingObj?.after && !isLoading) { + fetchFeedHandler(pagingObj.after); + } + }; + + const handleFeedFilterChange = useCallback( + (feedType, threadType) => { + fetchFeedHandler(paging.after, feedType, threadType); + }, + [paging] + ); + + useEffect(() => { + fetchMoreThread(isInView as boolean, paging, isEntityThreadLoading); + }, [paging, isEntityThreadLoading, isInView]); + useEffect(() => { setFollowersData(mlModelDetail.followers || []); }, [ @@ -446,6 +481,14 @@ const MlModelDetail: FC = ({ = ({ ? onTierUpdate : undefined } + onThreadLinkSelect={handleThreadLinkSelect} />
@@ -487,6 +531,14 @@ const MlModelDetail: FC = ({ = ({ onCancel={onCancel} onDescriptionEdit={onDescriptionEdit} onDescriptionUpdate={onDescriptionUpdate} + onThreadLinkSelect={handleThreadLinkSelect} /> = ({ )} {activeTab === 2 && ( + + + + + + )} + {activeTab === 3 && (
{getMlHyperParameters()} {getMlModelStore()}
)} - {activeTab === 3 && ( + {activeTab === 4 && (
@@ -536,7 +605,7 @@ const MlModelDetail: FC = ({ />
)} - {activeTab === 4 && ( + {activeTab === 5 && ( = ({ handleExtentionUpdate={onExtensionUpdate} /> )} +
}> + {isEntityThreadLoading ? : null} +
+ {threadLink ? ( + + ) : null} ); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/MlModelDetail/MlModelDetail.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/MlModelDetail/MlModelDetail.interface.ts new file mode 100644 index 00000000000..c779e4db053 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/MlModelDetail/MlModelDetail.interface.ts @@ -0,0 +1,72 @@ +/* + * Copyright 2021 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 { + EntityFieldThreadCount, + EntityReference, + LeafNodes, + LineagePos, + LoadingNodeState, +} from 'Models'; +import { HTMLAttributes } from 'react'; +import { FeedFilter } from '../../enums/mydata.enum'; +import { CreateThread } from '../../generated/api/feed/createThread'; +import { Mlmodel } from '../../generated/entity/data/mlmodel'; +import { Thread, ThreadType } from '../../generated/entity/feed/thread'; +import { EntityLineage } from '../../generated/type/entityLineage'; +import { Paging } from '../../generated/type/paging'; +import { ThreadUpdatedFunc } from '../../interface/feed.interface'; +import { Edge, EdgeData } from '../EntityLineage/EntityLineage.interface'; + +export interface MlModelDetailProp extends HTMLAttributes { + mlModelDetail: Mlmodel; + activeTab: number; + entityThread: Thread[]; + isEntityThreadLoading: boolean; + paging: Paging; + feedCount: number; + followMlModelHandler: () => void; + unfollowMlModelHandler: () => void; + descriptionUpdateHandler: (updatedMlModel: Mlmodel) => Promise; + setActiveTabHandler: (value: number) => void; + tagUpdateHandler: (updatedMlModel: Mlmodel) => void; + updateMlModelFeatures: (updatedMlModel: Mlmodel) => Promise; + settingsUpdateHandler: (updatedMlModel: Mlmodel) => Promise; + lineageTabData: { + loadNodeHandler: (node: EntityReference, pos: LineagePos) => void; + addLineageHandler: (edge: Edge) => Promise; + removeLineageHandler: (data: EdgeData) => void; + entityLineageHandler: (lineage: EntityLineage) => void; + isLineageLoading?: boolean; + entityLineage: EntityLineage; + lineageLeafNodes: LeafNodes; + isNodeLoading: LoadingNodeState; + }; + onExtensionUpdate: (updatedMlModel: Mlmodel) => Promise; + fetchFeedHandler: ( + after?: string, + feedType?: FeedFilter, + threadType?: ThreadType + ) => void; + postFeedHandler: (value: string, id: string) => void; + deletePostHandler: ( + threadId: string, + postId: string, + isThread: boolean + ) => void; + + updateThreadHandler: ThreadUpdatedFunc; + entityFieldThreadCount: EntityFieldThreadCount[]; + entityFieldTaskCount: EntityFieldThreadCount[]; + createThread: (data: CreateThread) => void; +} diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/AssigneeList/AssigneeList.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/AssigneeList/AssigneeList.tsx index b6f1d06a9f3..ad81bdb3d87 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/AssigneeList/AssigneeList.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/AssigneeList/AssigneeList.tsx @@ -35,6 +35,7 @@ const AssigneeList: FC = ({ assignees, className }) => { userName={assignee.name || ''}> { e.stopPropagation(); history.push(getUserPath(assignee.name ?? '')); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/description/Description.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/description/Description.test.tsx index 66183d21e00..3f15d05bfe8 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/description/Description.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/description/Description.test.tsx @@ -223,7 +223,7 @@ describe('Test Description Component', () => { const requestDescription = await findByTestId( container, - 'request-description' + 'request-entity-description' ); expect(descriptionContainer).toBeInTheDocument(); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/description/Description.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/description/Description.tsx index 98000cb8426..60b90088e21 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/description/Description.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/description/Description.tsx @@ -27,6 +27,7 @@ import SVGIcons, { Icons } from '../../../utils/SvgUtils'; import { getRequestDescriptionPath, getUpdateDescriptionPath, + TASK_ENTITIES, } from '../../../utils/TasksUtils'; import { showErrorToast } from '../../../utils/ToastUtils'; import { ModalWithMarkdownEditor } from '../../Modals/ModalWithMarkdownEditor/ModalWithMarkdownEditor'; @@ -46,7 +47,6 @@ const Description: FC = ({ entityName, entityFieldThreads, onThreadLinkSelect, - onEntityFieldSelect, entityType, entityFqn, entityFieldTasks, @@ -87,10 +87,10 @@ const Description: FC = ({ const RequestDescriptionEl = () => { const hasDescription = Boolean(description.trim()); - return onEntityFieldSelect ? ( + return TASK_ENTITIES.includes(entityType as EntityType) ? ( - - - ) : ( - - - {(hasEditAccess() || isCreator) && !isTaskClosed && ( - - )} - - )} - - - - - {isTaskDescription && ( - - )} - - {isTaskTags && ( - - )} - -
- {(hasEditAccess() || isCreator) && !isTaskClosed && ( - - )} - - {hasEditAccess() && !isTaskClosed && ( - - {taskDetail.task?.suggestion ? ( - setEditAssignee(false)}> - } - overlay={ - onTaskActionChange(info.key)} + + + ) : ( + + + {(hasEditAccess() || isCreator) && !isTaskClosed && ( + + )} + + )} +
+
+ + + {isTaskDescription && ( + + )} + + {isTaskTags && ( + + )} + +
+ {(hasEditAccess() || isCreator) && !isTaskClosed && ( )} - - )} -
- {isTaskClosed && } -
- - - - - - - {!isEmpty(taskFeedDetail) ? ( -
- - + {hasEditAccess() && !isTaskClosed && ( + + {taskDetail.task?.suggestion ? ( + + } + overlay={ + onTaskActionChange(info.key)} + /> + } + trigger={['click']} + type="primary" + onClick={onTaskResolve}> + {taskAction.label} + + ) : ( + + )} + + )}
- ) : null} -
- - {!isEmpty(taskFeedDetail) ? ( - - ) : null} - -
-
- + {isTaskClosed && } + + + + + + + + {!isEmpty(taskFeedDetail) ? ( +
+ + +
+ ) : null} +
+ + + {!isEmpty(taskFeedDetail) ? ( + + ) : null} + +
+
+ + )} + )} - + ); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/TasksPage.interface.ts b/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/TasksPage.interface.ts index 71194dc6dfe..aaf7fdefb0d 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/TasksPage.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/pages/TasksPage/TasksPage.interface.ts @@ -12,11 +12,12 @@ */ 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'; -export type EntityData = Table | Topic | Dashboard | Pipeline; +export type EntityData = Table | Topic | Dashboard | Pipeline | Mlmodel; export interface Option { label: string; diff --git a/openmetadata-ui/src/main/resources/ui/src/setupTests.js b/openmetadata-ui/src/main/resources/ui/src/setupTests.js index 19e0a955d88..dbcd28d905b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/setupTests.js +++ b/openmetadata-ui/src/main/resources/ui/src/setupTests.js @@ -45,3 +45,11 @@ window.ResizeObserver = jest.fn().mockImplementation(() => ({ unobserve: jest.fn(), disconnect: jest.fn(), })); + +/** + * mock implementation of IntersectionObserver + */ +window.IntersectionObserver = jest.fn().mockImplementation(() => ({ + observe: jest.fn(), + unobserve: jest.fn(), +})); diff --git a/openmetadata-ui/src/main/resources/ui/src/styles/app.less b/openmetadata-ui/src/main/resources/ui/src/styles/app.less index e470c9e2be7..814d2ea3b44 100644 --- a/openmetadata-ui/src/main/resources/ui/src/styles/app.less +++ b/openmetadata-ui/src/main/resources/ui/src/styles/app.less @@ -192,6 +192,12 @@ .mt-4 { margin-top: 1rem; } +.ml-4 { + margin-left: 1rem; +} +.ml-6 { + margin-left: 1.5rem; +} .mt-8 { margin-top: 2rem; } diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/AnnouncementsUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/AnnouncementsUtils.ts index 3a6f9acdf45..cc764cfbe78 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/AnnouncementsUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/AnnouncementsUtils.ts @@ -5,6 +5,7 @@ export const ANNOUNCEMENT_ENTITIES = [ EntityType.DASHBOARD, EntityType.TOPIC, EntityType.PIPELINE, + EntityType.MLMODEL, ]; export const validateMessages = { diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/MlModelDetailsUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/MlModelDetailsUtils.ts index 516730fc497..a2bbcc31e17 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/MlModelDetailsUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/MlModelDetailsUtils.ts @@ -21,6 +21,11 @@ export const mlModelTabs = [ name: 'Features', path: 'features', }, + { + name: 'Activity Feed', + path: 'activity_feed', + field: TabSpecificField.ACTIVITY_FEED, + }, { name: 'Details', path: 'details', @@ -39,17 +44,21 @@ export const mlModelTabs = [ export const getCurrentMlModelTab = (tab: string) => { let currentTab = 1; switch (tab) { - case 'details': + case 'activity_feed': currentTab = 2; break; - case 'lineage': + case 'details': currentTab = 3; break; - case 'custom_properties': + case 'lineage': currentTab = 4; + break; + case 'custom_properties': + currentTab = 5; + break; case 'features': diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/TasksUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/TasksUtils.ts index 6bdec92dc5c..04f845941e0 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/TasksUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/TasksUtils.ts @@ -16,6 +16,7 @@ import { Change, diffWordsWithSpace } from 'diff'; import { isEqual, isUndefined } from 'lodash'; import { getDashboardByFqn } from '../axiosAPIs/dashboardAPI'; import { getUserSuggestions } from '../axiosAPIs/miscAPI'; +import { getMlModelByFQN } from '../axiosAPIs/mlModelAPI'; import { getPipelineByFqn } from '../axiosAPIs/pipelineAPI'; import { getTableDetailsByFQN } from '../axiosAPIs/tableAPI'; import { getTopicByFqn } from '../axiosAPIs/topicsAPI'; @@ -40,6 +41,7 @@ import { import { getEntityName, getPartialNameFromTableFQN } from './CommonUtils'; import { defaultFields as DashboardFields } from './DashboardDetailsUtils'; import { defaultFields as TableFields } from './DatasetDetailsUtils'; +import { defaultFields as MlModelFields } from './MlModelDetailsUtils'; import { defaultFields as PipelineFields } from './PipelineDetailsUtils'; import { serviceTypeLogo } from './ServiceUtils'; import { getEntityLink } from './TableUtils'; @@ -183,6 +185,7 @@ export const TASK_ENTITIES = [ EntityType.DASHBOARD, EntityType.TOPIC, EntityType.PIPELINE, + EntityType.MLMODEL, ]; export const getBreadCrumbList = ( @@ -248,6 +251,10 @@ export const getBreadCrumbList = ( return [service(ServiceCategory.PIPELINE_SERVICES), activeEntity]; } + case EntityType.MLMODEL: { + return [service(ServiceCategory.ML_MODEL_SERVICES), activeEntity]; + } + default: return []; } @@ -291,6 +298,14 @@ export const fetchEntityDetail = ( .catch((err: AxiosError) => showErrorToast(err)); break; + case EntityType.MLMODEL: + getMlModelByFQN(entityFQN, MlModelFields) + .then((res) => { + setEntityData(res); + }) + .catch((err: AxiosError) => showErrorToast(err)); + + break; default: break;