mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-12-18 19:18:28 +00:00
* 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
This commit is contained in:
parent
3faef0e0cf
commit
786be3bedf
@ -263,6 +263,8 @@ export const LOGIN = {
|
|||||||
password: 'admin',
|
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 = {
|
export const HTTP_CONFIG_SOURCE = {
|
||||||
DBT_CATALOG_HTTP_PATH:
|
DBT_CATALOG_HTTP_PATH:
|
||||||
'https://raw.githubusercontent.com/OnkarVO7/dbt_git_test/master/catalog.json',
|
'https://raw.githubusercontent.com/OnkarVO7/dbt_git_test/master/catalog.json',
|
||||||
|
|||||||
@ -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)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
@ -171,7 +171,9 @@ const AnnouncementThreads: FC<ActivityThreadListProp> = ({
|
|||||||
{getAnnouncements(activeAnnouncements)}
|
{getAnnouncements(activeAnnouncements)}
|
||||||
{Boolean(inActiveAnnouncements.length) && (
|
{Boolean(inActiveAnnouncements.length) && (
|
||||||
<>
|
<>
|
||||||
<Typography.Text className="tw-block tw-mt-4 tw-font-medium">
|
<Typography.Text
|
||||||
|
className="tw-block tw-mt-4 tw-font-medium"
|
||||||
|
data-testid="inActive-announcements">
|
||||||
Inactive Announcements
|
Inactive Announcements
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
<Divider className="tw-mb-4 tw-mt-2" />
|
<Divider className="tw-mb-4 tw-mt-2" />
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import { LeafNodes } from 'Models';
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
import { Mlmodel } from '../../generated/entity/data/mlmodel';
|
import { Mlmodel } from '../../generated/entity/data/mlmodel';
|
||||||
|
import { Paging } from '../../generated/type/paging';
|
||||||
import MlModelDetailComponent from './MlModelDetail.component';
|
import MlModelDetailComponent from './MlModelDetail.component';
|
||||||
|
|
||||||
const mockData = {
|
const mockData = {
|
||||||
@ -153,6 +154,18 @@ const mockProp = {
|
|||||||
isNodeLoading: { id: undefined, state: false },
|
isNodeLoading: { id: undefined, state: false },
|
||||||
},
|
},
|
||||||
onExtensionUpdate: jest.fn(),
|
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', () => {
|
jest.mock('../common/description/Description', () => {
|
||||||
@ -179,6 +192,14 @@ jest.mock('../common/TabsPane/TabsPane', () => {
|
|||||||
return jest.fn().mockReturnValue(<p data-testid="tabs">Tabs</p>);
|
return jest.fn().mockReturnValue(<p data-testid="tabs">Tabs</p>);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
jest.mock('../ActivityFeed/ActivityFeedList/ActivityFeedList.tsx', () => {
|
||||||
|
return jest.fn().mockReturnValue(<p>ActivityFeedList</p>);
|
||||||
|
});
|
||||||
|
|
||||||
|
jest.mock('../ActivityFeed/ActivityThreadPanel/ActivityThreadPanel', () => {
|
||||||
|
return jest.fn().mockReturnValue(<p>ActivityThreadPanel</p>);
|
||||||
|
});
|
||||||
|
|
||||||
jest.mock('../../utils/CommonUtils', () => {
|
jest.mock('../../utils/CommonUtils', () => {
|
||||||
return {
|
return {
|
||||||
getEntityName: jest.fn().mockReturnValue('entityName'),
|
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 () => {
|
it('Should render hyper parameter and ml store table for details tab', async () => {
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
<MlModelDetailComponent {...mockProp} activeTab={2} />,
|
<MlModelDetailComponent {...mockProp} activeTab={3} />,
|
||||||
{
|
{
|
||||||
wrapper: MemoryRouter,
|
wrapper: MemoryRouter,
|
||||||
}
|
}
|
||||||
@ -245,7 +266,7 @@ describe('Test MlModel entity detail component', () => {
|
|||||||
|
|
||||||
it('Should render lineage tab', async () => {
|
it('Should render lineage tab', async () => {
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
<MlModelDetailComponent {...mockProp} activeTab={3} />,
|
<MlModelDetailComponent {...mockProp} activeTab={4} />,
|
||||||
{
|
{
|
||||||
wrapper: MemoryRouter,
|
wrapper: MemoryRouter,
|
||||||
}
|
}
|
||||||
@ -258,7 +279,7 @@ describe('Test MlModel entity detail component', () => {
|
|||||||
|
|
||||||
it('Check if active tab is custom properties', async () => {
|
it('Check if active tab is custom properties', async () => {
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
<MlModelDetailComponent {...mockProp} activeTab={4} />,
|
<MlModelDetailComponent {...mockProp} activeTab={5} />,
|
||||||
{
|
{
|
||||||
wrapper: MemoryRouter,
|
wrapper: MemoryRouter,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,22 +11,16 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { Table } from 'antd';
|
import { Col, Row, Table } from 'antd';
|
||||||
import { ColumnsType } from 'antd/lib/table';
|
import { ColumnsType } from 'antd/lib/table';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
import { isUndefined, startCase, uniqueId } from 'lodash';
|
import { isUndefined, startCase, uniqueId } from 'lodash';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import {
|
import { EntityTags, ExtraInfo } from 'Models';
|
||||||
EntityTags,
|
|
||||||
ExtraInfo,
|
|
||||||
LeafNodes,
|
|
||||||
LineagePos,
|
|
||||||
LoadingNodeState,
|
|
||||||
} from 'Models';
|
|
||||||
import React, {
|
import React, {
|
||||||
FC,
|
FC,
|
||||||
Fragment,
|
Fragment,
|
||||||
HTMLAttributes,
|
RefObject,
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
useMemo,
|
useMemo,
|
||||||
@ -38,24 +32,31 @@ import {
|
|||||||
getDashboardDetailsPath,
|
getDashboardDetailsPath,
|
||||||
getServiceDetailsPath,
|
getServiceDetailsPath,
|
||||||
} from '../../constants/constants';
|
} from '../../constants/constants';
|
||||||
|
import { EntityField } from '../../constants/feed.constants';
|
||||||
|
import { observerOptions } from '../../constants/Mydata.constants';
|
||||||
import { EntityType } from '../../enums/entity.enum';
|
import { EntityType } from '../../enums/entity.enum';
|
||||||
import { ServiceCategory } from '../../enums/service.enum';
|
import { ServiceCategory } from '../../enums/service.enum';
|
||||||
import { OwnerType } from '../../enums/user.enum';
|
import { OwnerType } from '../../enums/user.enum';
|
||||||
import { MlHyperParameter } from '../../generated/api/data/createMlModel';
|
import { MlHyperParameter } from '../../generated/api/data/createMlModel';
|
||||||
import { Mlmodel } from '../../generated/entity/data/mlmodel';
|
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 { EntityReference } from '../../generated/type/entityReference';
|
||||||
|
import { Paging } from '../../generated/type/paging';
|
||||||
import { LabelType, State, TagLabel } from '../../generated/type/tagLabel';
|
import { LabelType, State, TagLabel } from '../../generated/type/tagLabel';
|
||||||
|
import { useInfiniteScroll } from '../../hooks/useInfiniteScroll';
|
||||||
import jsonData from '../../jsons/en';
|
import jsonData from '../../jsons/en';
|
||||||
import {
|
import {
|
||||||
getEntityName,
|
getEntityName,
|
||||||
getEntityPlaceHolder,
|
getEntityPlaceHolder,
|
||||||
getOwnerValue,
|
getOwnerValue,
|
||||||
} from '../../utils/CommonUtils';
|
} from '../../utils/CommonUtils';
|
||||||
|
import { getEntityFieldThreadCounts } from '../../utils/FeedUtils';
|
||||||
import { DEFAULT_ENTITY_PERMISSION } from '../../utils/PermissionsUtils';
|
import { DEFAULT_ENTITY_PERMISSION } from '../../utils/PermissionsUtils';
|
||||||
import { serviceTypeLogo } from '../../utils/ServiceUtils';
|
import { serviceTypeLogo } from '../../utils/ServiceUtils';
|
||||||
import { getTagsWithoutTier, getTierTags } from '../../utils/TableUtils';
|
import { getTagsWithoutTier, getTierTags } from '../../utils/TableUtils';
|
||||||
import { showErrorToast } from '../../utils/ToastUtils';
|
import { showErrorToast } from '../../utils/ToastUtils';
|
||||||
|
import ActivityFeedList from '../ActivityFeed/ActivityFeedList/ActivityFeedList';
|
||||||
|
import ActivityThreadPanel from '../ActivityFeed/ActivityThreadPanel/ActivityThreadPanel';
|
||||||
import { CustomPropertyTable } from '../common/CustomPropertyTable/CustomPropertyTable';
|
import { CustomPropertyTable } from '../common/CustomPropertyTable/CustomPropertyTable';
|
||||||
import { CustomPropertyProps } from '../common/CustomPropertyTable/CustomPropertyTable.interface';
|
import { CustomPropertyProps } from '../common/CustomPropertyTable/CustomPropertyTable.interface';
|
||||||
import Description from '../common/description/Description';
|
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 { TitleBreadcrumbProps } from '../common/title-breadcrumb/title-breadcrumb.interface';
|
||||||
import PageContainer from '../containers/PageContainer';
|
import PageContainer from '../containers/PageContainer';
|
||||||
import EntityLineageComponent from '../EntityLineage/EntityLineage.component';
|
import EntityLineageComponent from '../EntityLineage/EntityLineage.component';
|
||||||
import { Edge, EdgeData } from '../EntityLineage/EntityLineage.interface';
|
import Loader from '../Loader/Loader';
|
||||||
import { usePermissionProvider } from '../PermissionProvider/PermissionProvider';
|
import { usePermissionProvider } from '../PermissionProvider/PermissionProvider';
|
||||||
import { ResourceEntity } from '../PermissionProvider/PermissionProvider.interface';
|
import { ResourceEntity } from '../PermissionProvider/PermissionProvider.interface';
|
||||||
|
import { MlModelDetailProp } from './MlModelDetail.interface';
|
||||||
import MlModelFeaturesList from './MlModelFeaturesList';
|
import MlModelFeaturesList from './MlModelFeaturesList';
|
||||||
|
|
||||||
interface MlModelDetailProp extends HTMLAttributes<HTMLDivElement> {
|
|
||||||
mlModelDetail: Mlmodel;
|
|
||||||
activeTab: number;
|
|
||||||
followMlModelHandler: () => void;
|
|
||||||
unfollowMlModelHandler: () => void;
|
|
||||||
descriptionUpdateHandler: (updatedMlModel: Mlmodel) => Promise<void>;
|
|
||||||
setActiveTabHandler: (value: number) => void;
|
|
||||||
tagUpdateHandler: (updatedMlModel: Mlmodel) => void;
|
|
||||||
updateMlModelFeatures: (updatedMlModel: Mlmodel) => Promise<void>;
|
|
||||||
settingsUpdateHandler: (updatedMlModel: Mlmodel) => Promise<void>;
|
|
||||||
lineageTabData: {
|
|
||||||
loadNodeHandler: (node: EntityReference, pos: LineagePos) => void;
|
|
||||||
addLineageHandler: (edge: Edge) => Promise<void>;
|
|
||||||
removeLineageHandler: (data: EdgeData) => void;
|
|
||||||
entityLineageHandler: (lineage: EntityLineage) => void;
|
|
||||||
isLineageLoading?: boolean;
|
|
||||||
entityLineage: EntityLineage;
|
|
||||||
lineageLeafNodes: LeafNodes;
|
|
||||||
isNodeLoading: LoadingNodeState;
|
|
||||||
};
|
|
||||||
onExtensionUpdate: (updatedMlModel: Mlmodel) => Promise<void>;
|
|
||||||
}
|
|
||||||
|
|
||||||
const MlModelDetail: FC<MlModelDetailProp> = ({
|
const MlModelDetail: FC<MlModelDetailProp> = ({
|
||||||
mlModelDetail,
|
mlModelDetail,
|
||||||
activeTab,
|
activeTab,
|
||||||
@ -104,6 +83,17 @@ const MlModelDetail: FC<MlModelDetailProp> = ({
|
|||||||
updateMlModelFeatures,
|
updateMlModelFeatures,
|
||||||
lineageTabData,
|
lineageTabData,
|
||||||
onExtensionUpdate,
|
onExtensionUpdate,
|
||||||
|
entityThread,
|
||||||
|
isEntityThreadLoading,
|
||||||
|
fetchFeedHandler,
|
||||||
|
deletePostHandler,
|
||||||
|
postFeedHandler,
|
||||||
|
updateThreadHandler,
|
||||||
|
paging,
|
||||||
|
feedCount,
|
||||||
|
createThread,
|
||||||
|
entityFieldTaskCount,
|
||||||
|
entityFieldThreadCount,
|
||||||
}) => {
|
}) => {
|
||||||
const [followersCount, setFollowersCount] = useState<number>(0);
|
const [followersCount, setFollowersCount] = useState<number>(0);
|
||||||
const [isFollowing, setIsFollowing] = useState<boolean>(false);
|
const [isFollowing, setIsFollowing] = useState<boolean>(false);
|
||||||
@ -114,6 +104,11 @@ const MlModelDetail: FC<MlModelDetailProp> = ({
|
|||||||
DEFAULT_ENTITY_PERMISSION
|
DEFAULT_ENTITY_PERMISSION
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [threadType, setThreadType] = useState<ThreadType>(
|
||||||
|
ThreadType.Conversation
|
||||||
|
);
|
||||||
|
const [threadLink, setThreadLink] = useState<string>('');
|
||||||
|
|
||||||
const { getEntityPermission } = usePermissionProvider();
|
const { getEntityPermission } = usePermissionProvider();
|
||||||
|
|
||||||
const fetchResourcePermission = useCallback(async () => {
|
const fetchResourcePermission = useCallback(async () => {
|
||||||
@ -130,6 +125,8 @@ const MlModelDetail: FC<MlModelDetailProp> = ({
|
|||||||
}
|
}
|
||||||
}, [mlModelDetail.id, getEntityPermission, setPipelinePermissions]);
|
}, [mlModelDetail.id, getEntityPermission, setPipelinePermissions]);
|
||||||
|
|
||||||
|
const [elementRef, isInView] = useInfiniteScroll(observerOptions);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (mlModelDetail.id) {
|
if (mlModelDetail.id) {
|
||||||
fetchResourcePermission();
|
fetchResourcePermission();
|
||||||
@ -232,6 +229,12 @@ const MlModelDetail: FC<MlModelDetailProp> = ({
|
|||||||
isProtected: false,
|
isProtected: false,
|
||||||
position: 1,
|
position: 1,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'Activity Feeds & Tasks',
|
||||||
|
isProtected: false,
|
||||||
|
position: 2,
|
||||||
|
count: feedCount,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'Details',
|
name: 'Details',
|
||||||
icon: {
|
icon: {
|
||||||
@ -241,17 +244,17 @@ const MlModelDetail: FC<MlModelDetailProp> = ({
|
|||||||
selectedName: 'icon-detailscolor',
|
selectedName: 'icon-detailscolor',
|
||||||
},
|
},
|
||||||
isProtected: false,
|
isProtected: false,
|
||||||
position: 2,
|
position: 3,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Lineage',
|
name: 'Lineage',
|
||||||
isProtected: false,
|
isProtected: false,
|
||||||
position: 3,
|
position: 4,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Custom Properties',
|
name: 'Custom Properties',
|
||||||
isProtected: false,
|
isProtected: false,
|
||||||
position: 4,
|
position: 5,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -341,6 +344,17 @@ const MlModelDetail: FC<MlModelDetailProp> = ({
|
|||||||
await updateMlModelFeatures({ ...mlModelDetail, mlFeatures: features });
|
await updateMlModelFeatures({ ...mlModelDetail, mlFeatures: features });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleThreadLinkSelect = (link: string, threadType?: ThreadType) => {
|
||||||
|
setThreadLink(link);
|
||||||
|
if (threadType) {
|
||||||
|
setThreadType(threadType);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleThreadPanelClose = () => {
|
||||||
|
setThreadLink('');
|
||||||
|
};
|
||||||
|
|
||||||
const getMlHyperParametersColumn: ColumnsType<MlHyperParameter> = useMemo(
|
const getMlHyperParametersColumn: ColumnsType<MlHyperParameter> = useMemo(
|
||||||
() => [
|
() => [
|
||||||
{
|
{
|
||||||
@ -430,6 +444,27 @@ const MlModelDetail: FC<MlModelDetailProp> = ({
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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(() => {
|
useEffect(() => {
|
||||||
setFollowersData(mlModelDetail.followers || []);
|
setFollowersData(mlModelDetail.followers || []);
|
||||||
}, [
|
}, [
|
||||||
@ -446,6 +481,14 @@ const MlModelDetail: FC<MlModelDetailProp> = ({
|
|||||||
<EntityPageInfo
|
<EntityPageInfo
|
||||||
canDelete={mlModelPermissions.Delete}
|
canDelete={mlModelPermissions.Delete}
|
||||||
deleted={mlModelDetail.deleted}
|
deleted={mlModelDetail.deleted}
|
||||||
|
entityFieldTasks={getEntityFieldThreadCounts(
|
||||||
|
EntityField.TAGS,
|
||||||
|
entityFieldTaskCount
|
||||||
|
)}
|
||||||
|
entityFieldThreads={getEntityFieldThreadCounts(
|
||||||
|
EntityField.TAGS,
|
||||||
|
entityFieldThreadCount
|
||||||
|
)}
|
||||||
entityFqn={mlModelDetail.fullyQualifiedName}
|
entityFqn={mlModelDetail.fullyQualifiedName}
|
||||||
entityId={mlModelDetail.id}
|
entityId={mlModelDetail.id}
|
||||||
entityName={mlModelDetail.name}
|
entityName={mlModelDetail.name}
|
||||||
@ -472,6 +515,7 @@ const MlModelDetail: FC<MlModelDetailProp> = ({
|
|||||||
? onTierUpdate
|
? onTierUpdate
|
||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
|
onThreadLinkSelect={handleThreadLinkSelect}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="tw-mt-4 tw-flex tw-flex-col tw-flex-grow">
|
<div className="tw-mt-4 tw-flex tw-flex-col tw-flex-grow">
|
||||||
@ -487,6 +531,14 @@ const MlModelDetail: FC<MlModelDetailProp> = ({
|
|||||||
<Fragment>
|
<Fragment>
|
||||||
<Description
|
<Description
|
||||||
description={mlModelDetail.description}
|
description={mlModelDetail.description}
|
||||||
|
entityFieldTasks={getEntityFieldThreadCounts(
|
||||||
|
EntityField.DESCRIPTION,
|
||||||
|
entityFieldTaskCount
|
||||||
|
)}
|
||||||
|
entityFieldThreads={getEntityFieldThreadCounts(
|
||||||
|
EntityField.DESCRIPTION,
|
||||||
|
entityFieldThreadCount
|
||||||
|
)}
|
||||||
entityFqn={mlModelDetail.fullyQualifiedName}
|
entityFqn={mlModelDetail.fullyQualifiedName}
|
||||||
entityName={mlModelDetail.name}
|
entityName={mlModelDetail.name}
|
||||||
entityType={EntityType.MLMODEL}
|
entityType={EntityType.MLMODEL}
|
||||||
@ -500,6 +552,7 @@ const MlModelDetail: FC<MlModelDetailProp> = ({
|
|||||||
onCancel={onCancel}
|
onCancel={onCancel}
|
||||||
onDescriptionEdit={onDescriptionEdit}
|
onDescriptionEdit={onDescriptionEdit}
|
||||||
onDescriptionUpdate={onDescriptionUpdate}
|
onDescriptionUpdate={onDescriptionUpdate}
|
||||||
|
onThreadLinkSelect={handleThreadLinkSelect}
|
||||||
/>
|
/>
|
||||||
<MlModelFeaturesList
|
<MlModelFeaturesList
|
||||||
handleFeaturesUpdate={onFeaturesUpdate}
|
handleFeaturesUpdate={onFeaturesUpdate}
|
||||||
@ -509,12 +562,28 @@ const MlModelDetail: FC<MlModelDetailProp> = ({
|
|||||||
</Fragment>
|
</Fragment>
|
||||||
)}
|
)}
|
||||||
{activeTab === 2 && (
|
{activeTab === 2 && (
|
||||||
|
<Row id="activityfeed">
|
||||||
|
<Col offset={3} span={18}>
|
||||||
|
<ActivityFeedList
|
||||||
|
isEntityFeed
|
||||||
|
withSidePanel
|
||||||
|
deletePostHandler={deletePostHandler}
|
||||||
|
entityName={mlModelDetail.name}
|
||||||
|
feedList={entityThread}
|
||||||
|
postFeedHandler={postFeedHandler}
|
||||||
|
updateThreadHandler={updateThreadHandler}
|
||||||
|
onFeedFiltersUpdate={handleFeedFilterChange}
|
||||||
|
/>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
)}
|
||||||
|
{activeTab === 3 && (
|
||||||
<div className="tw-grid tw-grid-cols-2 tw-gap-x-6">
|
<div className="tw-grid tw-grid-cols-2 tw-gap-x-6">
|
||||||
{getMlHyperParameters()}
|
{getMlHyperParameters()}
|
||||||
{getMlModelStore()}
|
{getMlModelStore()}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{activeTab === 3 && (
|
{activeTab === 4 && (
|
||||||
<div
|
<div
|
||||||
className="tw-px-2 tw-h-full"
|
className="tw-px-2 tw-h-full"
|
||||||
data-testid="lineage-details">
|
data-testid="lineage-details">
|
||||||
@ -536,7 +605,7 @@ const MlModelDetail: FC<MlModelDetailProp> = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{activeTab === 4 && (
|
{activeTab === 5 && (
|
||||||
<CustomPropertyTable
|
<CustomPropertyTable
|
||||||
entityDetails={
|
entityDetails={
|
||||||
mlModelDetail as CustomPropertyProps['entityDetails']
|
mlModelDetail as CustomPropertyProps['entityDetails']
|
||||||
@ -545,10 +614,28 @@ const MlModelDetail: FC<MlModelDetailProp> = ({
|
|||||||
handleExtentionUpdate={onExtensionUpdate}
|
handleExtentionUpdate={onExtensionUpdate}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
<div
|
||||||
|
data-testid="observer-element"
|
||||||
|
id="observer-element"
|
||||||
|
ref={elementRef as RefObject<HTMLDivElement>}>
|
||||||
|
{isEntityThreadLoading ? <Loader /> : null}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{threadLink ? (
|
||||||
|
<ActivityThreadPanel
|
||||||
|
createThread={createThread}
|
||||||
|
deletePostHandler={deletePostHandler}
|
||||||
|
open={Boolean(threadLink)}
|
||||||
|
postFeedHandler={postFeedHandler}
|
||||||
|
threadLink={threadLink}
|
||||||
|
threadType={threadType}
|
||||||
|
updateThreadHandler={updateThreadHandler}
|
||||||
|
onCancel={handleThreadPanelClose}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
</PageContainer>
|
</PageContainer>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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<HTMLDivElement> {
|
||||||
|
mlModelDetail: Mlmodel;
|
||||||
|
activeTab: number;
|
||||||
|
entityThread: Thread[];
|
||||||
|
isEntityThreadLoading: boolean;
|
||||||
|
paging: Paging;
|
||||||
|
feedCount: number;
|
||||||
|
followMlModelHandler: () => void;
|
||||||
|
unfollowMlModelHandler: () => void;
|
||||||
|
descriptionUpdateHandler: (updatedMlModel: Mlmodel) => Promise<void>;
|
||||||
|
setActiveTabHandler: (value: number) => void;
|
||||||
|
tagUpdateHandler: (updatedMlModel: Mlmodel) => void;
|
||||||
|
updateMlModelFeatures: (updatedMlModel: Mlmodel) => Promise<void>;
|
||||||
|
settingsUpdateHandler: (updatedMlModel: Mlmodel) => Promise<void>;
|
||||||
|
lineageTabData: {
|
||||||
|
loadNodeHandler: (node: EntityReference, pos: LineagePos) => void;
|
||||||
|
addLineageHandler: (edge: Edge) => Promise<void>;
|
||||||
|
removeLineageHandler: (data: EdgeData) => void;
|
||||||
|
entityLineageHandler: (lineage: EntityLineage) => void;
|
||||||
|
isLineageLoading?: boolean;
|
||||||
|
entityLineage: EntityLineage;
|
||||||
|
lineageLeafNodes: LeafNodes;
|
||||||
|
isNodeLoading: LoadingNodeState;
|
||||||
|
};
|
||||||
|
onExtensionUpdate: (updatedMlModel: Mlmodel) => Promise<void>;
|
||||||
|
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;
|
||||||
|
}
|
||||||
@ -35,6 +35,7 @@ const AssigneeList: FC<Props> = ({ assignees, className }) => {
|
|||||||
userName={assignee.name || ''}>
|
userName={assignee.name || ''}>
|
||||||
<span
|
<span
|
||||||
className="tw-flex tw-m-1.5 tw-mt-0 tw-cursor-pointer"
|
className="tw-flex tw-m-1.5 tw-mt-0 tw-cursor-pointer"
|
||||||
|
data-testid="assignee"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
history.push(getUserPath(assignee.name ?? ''));
|
history.push(getUserPath(assignee.name ?? ''));
|
||||||
|
|||||||
@ -223,7 +223,7 @@ describe('Test Description Component', () => {
|
|||||||
|
|
||||||
const requestDescription = await findByTestId(
|
const requestDescription = await findByTestId(
|
||||||
container,
|
container,
|
||||||
'request-description'
|
'request-entity-description'
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(descriptionContainer).toBeInTheDocument();
|
expect(descriptionContainer).toBeInTheDocument();
|
||||||
|
|||||||
@ -27,6 +27,7 @@ import SVGIcons, { Icons } from '../../../utils/SvgUtils';
|
|||||||
import {
|
import {
|
||||||
getRequestDescriptionPath,
|
getRequestDescriptionPath,
|
||||||
getUpdateDescriptionPath,
|
getUpdateDescriptionPath,
|
||||||
|
TASK_ENTITIES,
|
||||||
} from '../../../utils/TasksUtils';
|
} from '../../../utils/TasksUtils';
|
||||||
import { showErrorToast } from '../../../utils/ToastUtils';
|
import { showErrorToast } from '../../../utils/ToastUtils';
|
||||||
import { ModalWithMarkdownEditor } from '../../Modals/ModalWithMarkdownEditor/ModalWithMarkdownEditor';
|
import { ModalWithMarkdownEditor } from '../../Modals/ModalWithMarkdownEditor/ModalWithMarkdownEditor';
|
||||||
@ -46,7 +47,6 @@ const Description: FC<DescriptionProps> = ({
|
|||||||
entityName,
|
entityName,
|
||||||
entityFieldThreads,
|
entityFieldThreads,
|
||||||
onThreadLinkSelect,
|
onThreadLinkSelect,
|
||||||
onEntityFieldSelect,
|
|
||||||
entityType,
|
entityType,
|
||||||
entityFqn,
|
entityFqn,
|
||||||
entityFieldTasks,
|
entityFieldTasks,
|
||||||
@ -87,10 +87,10 @@ const Description: FC<DescriptionProps> = ({
|
|||||||
const RequestDescriptionEl = () => {
|
const RequestDescriptionEl = () => {
|
||||||
const hasDescription = Boolean(description.trim());
|
const hasDescription = Boolean(description.trim());
|
||||||
|
|
||||||
return onEntityFieldSelect ? (
|
return TASK_ENTITIES.includes(entityType as EntityType) ? (
|
||||||
<button
|
<button
|
||||||
className="tw-w-7 tw-h-7 tw-flex-none link-text focus:tw-outline-none"
|
className="tw-w-7 tw-h-7 tw-flex-none link-text focus:tw-outline-none"
|
||||||
data-testid="request-description"
|
data-testid="request-entity-description"
|
||||||
onClick={
|
onClick={
|
||||||
hasDescription ? handleUpdateDescription : handleRequestDescription
|
hasDescription ? handleUpdateDescription : handleRequestDescription
|
||||||
}>
|
}>
|
||||||
|
|||||||
@ -297,8 +297,7 @@ const EntityPageInfo = ({
|
|||||||
|
|
||||||
const getThreadElements = () => {
|
const getThreadElements = () => {
|
||||||
if (!isUndefined(entityFieldThreads)) {
|
if (!isUndefined(entityFieldThreads)) {
|
||||||
return !isUndefined(tagThread) &&
|
return !isUndefined(tagThread) ? (
|
||||||
TASK_ENTITIES.includes(entityType as EntityType) ? (
|
|
||||||
<button
|
<button
|
||||||
className="tw-w-7 tw-h-7 tw-flex-none link-text focus:tw-outline-none"
|
className="tw-w-7 tw-h-7 tw-flex-none link-text focus:tw-outline-none"
|
||||||
data-testid="tag-thread"
|
data-testid="tag-thread"
|
||||||
@ -337,10 +336,11 @@ const EntityPageInfo = ({
|
|||||||
const hasTags = !isEmpty(tags);
|
const hasTags = !isEmpty(tags);
|
||||||
const text = hasTags ? 'Update request tags' : 'Request tags';
|
const text = hasTags ? 'Update request tags' : 'Request tags';
|
||||||
|
|
||||||
return onThreadLinkSelect ? (
|
return onThreadLinkSelect &&
|
||||||
|
TASK_ENTITIES.includes(entityType as EntityType) ? (
|
||||||
<button
|
<button
|
||||||
className="tw-w-7 tw-h-7 tw-mr-1 tw-flex-none link-text focus:tw-outline-none tw-align-top"
|
className="tw-w-7 tw-h-7 tw-mr-1 tw-flex-none link-text focus:tw-outline-none tw-align-top"
|
||||||
data-testid="request-description"
|
data-testid="request-entity-tags"
|
||||||
onClick={hasTags ? handleUpdateTags : handleRequestTags}>
|
onClick={hasTags ? handleUpdateTags : handleRequestTags}>
|
||||||
<Popover
|
<Popover
|
||||||
destroyTooltipOnHide
|
destroyTooltipOnHide
|
||||||
|
|||||||
@ -12,12 +12,23 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError } from 'axios';
|
||||||
import { compare } from 'fast-json-patch';
|
import { compare, Operation } from 'fast-json-patch';
|
||||||
import { isEmpty, isNil } from 'lodash';
|
import { isEmpty, isNil } from 'lodash';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import { LeafNodes, LineagePos, LoadingNodeState } from 'Models';
|
import {
|
||||||
import React, { Fragment, useEffect, useState } from 'react';
|
EntityFieldThreadCount,
|
||||||
|
LeafNodes,
|
||||||
|
LineagePos,
|
||||||
|
LoadingNodeState,
|
||||||
|
} from 'Models';
|
||||||
|
import React, { Fragment, useEffect, useMemo, useState } from 'react';
|
||||||
import { useHistory, useParams } from 'react-router-dom';
|
import { useHistory, useParams } from 'react-router-dom';
|
||||||
|
import AppState from '../../AppState';
|
||||||
|
import {
|
||||||
|
getAllFeeds,
|
||||||
|
postFeedById,
|
||||||
|
postThread,
|
||||||
|
} from '../../axiosAPIs/feedsAPI';
|
||||||
import { getLineageByFQN } from '../../axiosAPIs/lineageAPI';
|
import { getLineageByFQN } from '../../axiosAPIs/lineageAPI';
|
||||||
import { addLineage, deleteLineageEdge } from '../../axiosAPIs/miscAPI';
|
import { addLineage, deleteLineageEdge } from '../../axiosAPIs/miscAPI';
|
||||||
import {
|
import {
|
||||||
@ -38,17 +49,23 @@ import { ResourceEntity } from '../../components/PermissionProvider/PermissionPr
|
|||||||
import { getMlModelPath } from '../../constants/constants';
|
import { getMlModelPath } from '../../constants/constants';
|
||||||
import { NO_PERMISSION_TO_VIEW } from '../../constants/HelperTextUtil';
|
import { NO_PERMISSION_TO_VIEW } from '../../constants/HelperTextUtil';
|
||||||
import { EntityType, TabSpecificField } from '../../enums/entity.enum';
|
import { EntityType, TabSpecificField } from '../../enums/entity.enum';
|
||||||
|
import { FeedFilter } from '../../enums/mydata.enum';
|
||||||
|
import { CreateThread } from '../../generated/api/feed/createThread';
|
||||||
import { Mlmodel } from '../../generated/entity/data/mlmodel';
|
import { Mlmodel } from '../../generated/entity/data/mlmodel';
|
||||||
|
import { Post, Thread, ThreadType } from '../../generated/entity/feed/thread';
|
||||||
import {
|
import {
|
||||||
EntityLineage,
|
EntityLineage,
|
||||||
EntityReference,
|
EntityReference,
|
||||||
} from '../../generated/type/entityLineage';
|
} from '../../generated/type/entityLineage';
|
||||||
|
import { Paging } from '../../generated/type/paging';
|
||||||
import jsonData from '../../jsons/en';
|
import jsonData from '../../jsons/en';
|
||||||
import {
|
import {
|
||||||
getCurrentUserId,
|
getCurrentUserId,
|
||||||
getEntityMissingError,
|
getEntityMissingError,
|
||||||
|
getFeedCounts,
|
||||||
} from '../../utils/CommonUtils';
|
} from '../../utils/CommonUtils';
|
||||||
import { getEntityLineage } from '../../utils/EntityUtils';
|
import { getEntityFeedLink, getEntityLineage } from '../../utils/EntityUtils';
|
||||||
|
import { deletePost, updateThreadData } from '../../utils/FeedUtils';
|
||||||
import {
|
import {
|
||||||
defaultFields,
|
defaultFields,
|
||||||
getCurrentMlModelTab,
|
getCurrentMlModelTab,
|
||||||
@ -79,6 +96,25 @@ const MlModelPage = () => {
|
|||||||
DEFAULT_ENTITY_PERMISSION
|
DEFAULT_ENTITY_PERMISSION
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const [entityThread, setEntityThread] = useState<Thread[]>([]);
|
||||||
|
const [isEntityThreadLoading, setIsEntityThreadLoading] =
|
||||||
|
useState<boolean>(false);
|
||||||
|
const [paging, setPaging] = useState<Paging>({} as Paging);
|
||||||
|
|
||||||
|
const [feedCount, setFeedCount] = useState<number>(0);
|
||||||
|
const [entityFieldThreadCount, setEntityFieldThreadCount] = useState<
|
||||||
|
EntityFieldThreadCount[]
|
||||||
|
>([]);
|
||||||
|
const [entityFieldTaskCount, setEntityFieldTaskCount] = useState<
|
||||||
|
EntityFieldThreadCount[]
|
||||||
|
>([]);
|
||||||
|
|
||||||
|
// get current user details
|
||||||
|
const currentUser = useMemo(
|
||||||
|
() => AppState.getCurrentUserDetails(),
|
||||||
|
[AppState.userDetails, AppState.nonSecureUserDetails]
|
||||||
|
);
|
||||||
|
|
||||||
const { getEntityPermissionByFqn } = usePermissionProvider();
|
const { getEntityPermissionByFqn } = usePermissionProvider();
|
||||||
|
|
||||||
const fetchResourcePermission = async (entityFqn: string) => {
|
const fetchResourcePermission = async (entityFqn: string) => {
|
||||||
@ -100,7 +136,7 @@ const MlModelPage = () => {
|
|||||||
|
|
||||||
const getLineageData = () => {
|
const getLineageData = () => {
|
||||||
setIsLineageLoading(true);
|
setIsLineageLoading(true);
|
||||||
getLineageByFQN(mlModelDetail.fullyQualifiedName ?? '', EntityType.MLMODEL)
|
getLineageByFQN(mlModelFqn, EntityType.MLMODEL)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res) {
|
if (res) {
|
||||||
setEntityLineage(res);
|
setEntityLineage(res);
|
||||||
@ -202,10 +238,57 @@ const MlModelPage = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const fetchEntityFeedCount = () => {
|
||||||
|
getFeedCounts(
|
||||||
|
EntityType.MLMODEL,
|
||||||
|
mlModelFqn,
|
||||||
|
setEntityFieldThreadCount,
|
||||||
|
setEntityFieldTaskCount,
|
||||||
|
setFeedCount
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchFeedData = async (
|
||||||
|
after?: string,
|
||||||
|
feedType?: FeedFilter,
|
||||||
|
threadType?: ThreadType
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
setIsEntityThreadLoading(true);
|
||||||
|
const response = await getAllFeeds(
|
||||||
|
getEntityFeedLink(EntityType.MLMODEL, mlModelFqn),
|
||||||
|
after,
|
||||||
|
threadType,
|
||||||
|
feedType,
|
||||||
|
undefined,
|
||||||
|
USERId
|
||||||
|
);
|
||||||
|
const { data, paging: pagingObj } = response;
|
||||||
|
setPaging(pagingObj);
|
||||||
|
setEntityThread((prevData) => [...prevData, ...data]);
|
||||||
|
} catch (error) {
|
||||||
|
showErrorToast(
|
||||||
|
error as AxiosError,
|
||||||
|
jsonData['api-error-messages']['fetch-entity-feed-error']
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
setIsEntityThreadLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleFeedFetchFromFeedList = (
|
||||||
|
after?: string,
|
||||||
|
feedType?: FeedFilter,
|
||||||
|
threadType?: ThreadType
|
||||||
|
) => {
|
||||||
|
!after && setEntityThread([]);
|
||||||
|
fetchFeedData(after, feedType, threadType);
|
||||||
|
};
|
||||||
|
|
||||||
const fetchTabSpecificData = (tabField = '') => {
|
const fetchTabSpecificData = (tabField = '') => {
|
||||||
switch (tabField) {
|
switch (tabField) {
|
||||||
case TabSpecificField.LINEAGE: {
|
case TabSpecificField.LINEAGE: {
|
||||||
if (!isEmpty(mlModelDetail) && !mlModelDetail.deleted) {
|
if (!mlModelDetail.deleted) {
|
||||||
if (isEmpty(entityLineage)) {
|
if (isEmpty(entityLineage)) {
|
||||||
getLineageData();
|
getLineageData();
|
||||||
}
|
}
|
||||||
@ -215,6 +298,11 @@ const MlModelPage = () => {
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case TabSpecificField.ACTIVITY_FEED: {
|
||||||
|
fetchFeedData();
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@ -391,13 +479,77 @@ const MlModelPage = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const postFeedHandler = async (value: string, threadId: string) => {
|
||||||
|
const data = {
|
||||||
|
message: value,
|
||||||
|
from: currentUser?.name,
|
||||||
|
} as Post;
|
||||||
|
try {
|
||||||
|
const response = await postFeedById(threadId, data);
|
||||||
|
const { id, posts } = response;
|
||||||
|
setEntityThread((pre) => {
|
||||||
|
return pre.map((thread) => {
|
||||||
|
if (thread.id === id) {
|
||||||
|
return { ...response, posts: posts?.slice(-3) };
|
||||||
|
} else {
|
||||||
|
return thread;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
fetchEntityFeedCount();
|
||||||
|
} catch (error) {
|
||||||
|
showErrorToast(
|
||||||
|
error as AxiosError,
|
||||||
|
jsonData['api-error-messages']['add-feed-error']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const createThread = async (data: CreateThread) => {
|
||||||
|
try {
|
||||||
|
const response = await postThread(data);
|
||||||
|
setEntityThread((pre) => [...pre, response]);
|
||||||
|
fetchEntityFeedCount();
|
||||||
|
} catch (error) {
|
||||||
|
showErrorToast(
|
||||||
|
error as AxiosError,
|
||||||
|
jsonData['api-error-messages']['create-conversation-error']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const deletePostHandler = (
|
||||||
|
threadId: string,
|
||||||
|
postId: string,
|
||||||
|
isThread: boolean
|
||||||
|
) => {
|
||||||
|
deletePost(threadId, postId, isThread, setEntityThread);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateThreadHandler = (
|
||||||
|
threadId: string,
|
||||||
|
postId: string,
|
||||||
|
isThread: boolean,
|
||||||
|
data: Operation[]
|
||||||
|
) => {
|
||||||
|
updateThreadData(threadId, postId, isThread, data, setEntityThread);
|
||||||
|
};
|
||||||
|
|
||||||
const getMlModelDetail = () => {
|
const getMlModelDetail = () => {
|
||||||
if (!isNil(mlModelDetail) && !isEmpty(mlModelDetail)) {
|
if (!isNil(mlModelDetail) && !isEmpty(mlModelDetail)) {
|
||||||
return (
|
return (
|
||||||
<MlModelDetailComponent
|
<MlModelDetailComponent
|
||||||
activeTab={activeTab}
|
activeTab={activeTab}
|
||||||
|
createThread={createThread}
|
||||||
|
deletePostHandler={deletePostHandler}
|
||||||
descriptionUpdateHandler={descriptionUpdateHandler}
|
descriptionUpdateHandler={descriptionUpdateHandler}
|
||||||
|
entityFieldTaskCount={entityFieldTaskCount}
|
||||||
|
entityFieldThreadCount={entityFieldThreadCount}
|
||||||
|
entityThread={entityThread}
|
||||||
|
feedCount={feedCount}
|
||||||
|
fetchFeedHandler={handleFeedFetchFromFeedList}
|
||||||
followMlModelHandler={followMlModel}
|
followMlModelHandler={followMlModel}
|
||||||
|
isEntityThreadLoading={isEntityThreadLoading}
|
||||||
lineageTabData={{
|
lineageTabData={{
|
||||||
loadNodeHandler,
|
loadNodeHandler,
|
||||||
addLineageHandler,
|
addLineageHandler,
|
||||||
@ -409,11 +561,14 @@ const MlModelPage = () => {
|
|||||||
isNodeLoading,
|
isNodeLoading,
|
||||||
}}
|
}}
|
||||||
mlModelDetail={mlModelDetail}
|
mlModelDetail={mlModelDetail}
|
||||||
|
paging={paging}
|
||||||
|
postFeedHandler={postFeedHandler}
|
||||||
setActiveTabHandler={activeTabHandler}
|
setActiveTabHandler={activeTabHandler}
|
||||||
settingsUpdateHandler={settingsUpdateHandler}
|
settingsUpdateHandler={settingsUpdateHandler}
|
||||||
tagUpdateHandler={onTagUpdate}
|
tagUpdateHandler={onTagUpdate}
|
||||||
unfollowMlModelHandler={unfollowMlModel}
|
unfollowMlModelHandler={unfollowMlModel}
|
||||||
updateMlModelFeatures={updateMlModelFeatures}
|
updateMlModelFeatures={updateMlModelFeatures}
|
||||||
|
updateThreadHandler={updateThreadHandler}
|
||||||
onExtensionUpdate={handleExtentionUpdate}
|
onExtensionUpdate={handleExtentionUpdate}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -426,13 +581,18 @@ const MlModelPage = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setEntityThread([]);
|
||||||
|
}, [tab]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchTabSpecificData(mlModelTabs[activeTab - 1].field);
|
fetchTabSpecificData(mlModelTabs[activeTab - 1].field);
|
||||||
}, [activeTab, mlModelDetail]);
|
}, [activeTab]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (mlModelPermissions.ViewAll || mlModelPermissions.ViewBasic) {
|
if (mlModelPermissions.ViewAll || mlModelPermissions.ViewBasic) {
|
||||||
fetchMlModelDetails(mlModelFqn);
|
fetchMlModelDetails(mlModelFqn);
|
||||||
|
fetchEntityFeedCount();
|
||||||
}
|
}
|
||||||
}, [mlModelPermissions, mlModelFqn]);
|
}, [mlModelPermissions, mlModelFqn]);
|
||||||
|
|
||||||
|
|||||||
@ -230,7 +230,7 @@ const RequestTag = () => {
|
|||||||
Back
|
Back
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
data-testid="submit-test"
|
data-testid="submit-tag-request"
|
||||||
htmlType="submit"
|
htmlType="submit"
|
||||||
type="primary">
|
type="primary">
|
||||||
{suggestion ? 'Suggest' : 'Submit'}
|
{suggestion ? 'Suggest' : 'Submit'}
|
||||||
|
|||||||
@ -41,6 +41,7 @@ import ErrorPlaceHolder from '../../../components/common/error-with-placeholder/
|
|||||||
import UserPopOverCard from '../../../components/common/PopOverCard/UserPopOverCard';
|
import UserPopOverCard from '../../../components/common/PopOverCard/UserPopOverCard';
|
||||||
import ProfilePicture from '../../../components/common/ProfilePicture/ProfilePicture';
|
import ProfilePicture from '../../../components/common/ProfilePicture/ProfilePicture';
|
||||||
import TitleBreadcrumb from '../../../components/common/title-breadcrumb/title-breadcrumb.component';
|
import TitleBreadcrumb from '../../../components/common/title-breadcrumb/title-breadcrumb.component';
|
||||||
|
import Loader from '../../../components/Loader/Loader';
|
||||||
import { FQN_SEPARATOR_CHAR } from '../../../constants/char.constants';
|
import { FQN_SEPARATOR_CHAR } from '../../../constants/char.constants';
|
||||||
import { PanelTab, TaskOperation } from '../../../constants/feed.constants';
|
import { PanelTab, TaskOperation } from '../../../constants/feed.constants';
|
||||||
import { EntityType } from '../../../enums/entity.enum';
|
import { EntityType } from '../../../enums/entity.enum';
|
||||||
@ -116,6 +117,7 @@ const TaskDetailPage = () => {
|
|||||||
const [modalVisible, setModalVisible] = useState<boolean>(false);
|
const [modalVisible, setModalVisible] = useState<boolean>(false);
|
||||||
const [comment, setComment] = useState<string>('');
|
const [comment, setComment] = useState<string>('');
|
||||||
const [tagsSuggestion, setTagsSuggestion] = useState<TagLabel[]>([]);
|
const [tagsSuggestion, setTagsSuggestion] = useState<TagLabel[]>([]);
|
||||||
|
const [isTaskLoading, setIsTaskLoading] = useState<boolean>(false);
|
||||||
|
|
||||||
// get current user details
|
// get current user details
|
||||||
const currentUser = useMemo(
|
const currentUser = useMemo(
|
||||||
@ -144,7 +146,7 @@ const TaskDetailPage = () => {
|
|||||||
const columnName = columnValue.split(FQN_SEPARATOR_CHAR).pop();
|
const columnName = columnValue.split(FQN_SEPARATOR_CHAR).pop();
|
||||||
|
|
||||||
return getColumnObject(
|
return getColumnObject(
|
||||||
columnName as string,
|
columnName ?? '',
|
||||||
(entityData as Table).columns || []
|
(entityData as Table).columns || []
|
||||||
);
|
);
|
||||||
}, [taskDetail, entityData]);
|
}, [taskDetail, entityData]);
|
||||||
@ -173,14 +175,16 @@ const TaskDetailPage = () => {
|
|||||||
|
|
||||||
const isTaskTags = isTagsTask(taskDetail.task?.type as TaskType);
|
const isTaskTags = isTagsTask(taskDetail.task?.type as TaskType);
|
||||||
|
|
||||||
const fetchTaskDetail = () => {
|
const fetchTaskDetail = async () => {
|
||||||
getTask(taskId)
|
setIsTaskLoading(true);
|
||||||
.then((res) => {
|
try {
|
||||||
setTaskDetail(res);
|
const data = await getTask(taskId);
|
||||||
})
|
setTaskDetail(data);
|
||||||
.catch((err: AxiosError) => {
|
} catch (error) {
|
||||||
showErrorToast(err, '', 5000, setError);
|
showErrorToast(error as AxiosError, '', 5000, setError);
|
||||||
});
|
} finally {
|
||||||
|
setIsTaskLoading(false);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchTaskFeed = (id: string) => {
|
const fetchTaskFeed = (id: string) => {
|
||||||
@ -309,8 +313,8 @@ const TaskDetailPage = () => {
|
|||||||
showSuccessToast('Task Resolved Successfully');
|
showSuccessToast('Task Resolved Successfully');
|
||||||
history.push(
|
history.push(
|
||||||
getEntityLink(
|
getEntityLink(
|
||||||
entityType as string,
|
entityType ?? '',
|
||||||
entityData?.fullyQualifiedName as string
|
entityData?.fullyQualifiedName ?? ''
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
@ -345,8 +349,8 @@ const TaskDetailPage = () => {
|
|||||||
showSuccessToast('Task Closed Successfully');
|
showSuccessToast('Task Closed Successfully');
|
||||||
history.push(
|
history.push(
|
||||||
getEntityLink(
|
getEntityLink(
|
||||||
entityType as string,
|
entityType ?? '',
|
||||||
entityData?.fullyQualifiedName as string
|
entityData?.fullyQualifiedName ?? ''
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
@ -458,7 +462,7 @@ const TaskDetailPage = () => {
|
|||||||
entityFQN &&
|
entityFQN &&
|
||||||
fetchEntityDetail(
|
fetchEntityDetail(
|
||||||
entityType as EntityType,
|
entityType as EntityType,
|
||||||
getEncodedFqn(entityFQN as string),
|
getEncodedFqn(entityFQN ?? ''),
|
||||||
setEntityData
|
setEntityData
|
||||||
);
|
);
|
||||||
fetchTaskFeed(taskDetail.id);
|
fetchTaskFeed(taskDetail.id);
|
||||||
@ -467,9 +471,9 @@ const TaskDetailPage = () => {
|
|||||||
const taskAssignees = taskDetail.task?.assignees || [];
|
const taskAssignees = taskDetail.task?.assignees || [];
|
||||||
if (taskAssignees.length) {
|
if (taskAssignees.length) {
|
||||||
const assigneesArr = taskAssignees.map((assignee) => ({
|
const assigneesArr = taskAssignees.map((assignee) => ({
|
||||||
label: assignee.name as string,
|
label: assignee.name ?? '',
|
||||||
value: assignee.id,
|
value: assignee.id,
|
||||||
type: assignee.type as string,
|
type: assignee.type ?? '',
|
||||||
}));
|
}));
|
||||||
setAssignees(assigneesArr);
|
setAssignees(assigneesArr);
|
||||||
setOptions(assigneesArr);
|
setOptions(assigneesArr);
|
||||||
@ -506,252 +510,262 @@ const TaskDetailPage = () => {
|
|||||||
isAdminUser || isAuthDisabled || isAssignee || isOwner;
|
isAdminUser || isAuthDisabled || isAssignee || isOwner;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout style={{ ...background, height: '100vh' }}>
|
<>
|
||||||
{error ? (
|
{isTaskLoading ? (
|
||||||
<ErrorPlaceHolder>{error}</ErrorPlaceHolder>
|
<Loader />
|
||||||
) : (
|
) : (
|
||||||
<Fragment>
|
<Layout style={{ ...background, height: '100vh' }}>
|
||||||
<Content style={{ ...contentStyles, overflowY: 'auto' }}>
|
{error ? (
|
||||||
<TitleBreadcrumb
|
<ErrorPlaceHolder>{error}</ErrorPlaceHolder>
|
||||||
titleLinks={[
|
) : (
|
||||||
...getBreadCrumbList(entityData, entityType as EntityType),
|
<Fragment>
|
||||||
{
|
<Content style={{ ...contentStyles, overflowY: 'auto' }}>
|
||||||
name: `Task #${taskDetail.task?.id}`,
|
<TitleBreadcrumb
|
||||||
activeTitle: true,
|
titleLinks={[
|
||||||
url: '',
|
...getBreadCrumbList(entityData, entityType as EntityType),
|
||||||
},
|
{
|
||||||
]}
|
name: `Task #${taskDetail.task?.id}`,
|
||||||
/>
|
activeTitle: true,
|
||||||
<EntityDetail entityData={entityData} />
|
url: '',
|
||||||
|
},
|
||||||
<Card
|
]}
|
||||||
data-testid="task-metadata"
|
|
||||||
style={{ ...cardStyles, marginTop: '16px' }}>
|
|
||||||
<p
|
|
||||||
className="tw-text-base tw-font-medium tw-mb-4"
|
|
||||||
data-testid="task-title">
|
|
||||||
{`Task #${taskId}`} {taskDetail.message}
|
|
||||||
</p>
|
|
||||||
<div className="tw-flex tw-mb-4" data-testid="task-metadata">
|
|
||||||
<TaskStatus
|
|
||||||
status={taskDetail.task?.status as ThreadTaskStatus}
|
|
||||||
/>
|
/>
|
||||||
<span className="tw-mx-1.5 tw-inline-block tw-text-gray-400">
|
<EntityDetail entityData={entityData} />
|
||||||
|
|
|
||||||
</span>
|
<Card
|
||||||
<span className="tw-flex">
|
data-testid="task-metadata"
|
||||||
<UserPopOverCard userName={taskDetail.createdBy || ''}>
|
style={{ ...cardStyles, marginTop: '16px' }}>
|
||||||
|
<p
|
||||||
|
className="tw-text-base tw-font-medium tw-mb-4"
|
||||||
|
data-testid="task-title">
|
||||||
|
{`Task #${taskId}`} {taskDetail.message}
|
||||||
|
</p>
|
||||||
|
<div className="tw-flex tw-mb-4" data-testid="task-metadata">
|
||||||
|
<TaskStatus
|
||||||
|
status={taskDetail.task?.status as ThreadTaskStatus}
|
||||||
|
/>
|
||||||
|
<span className="tw-mx-1.5 tw-inline-block tw-text-gray-400">
|
||||||
|
|
|
||||||
|
</span>
|
||||||
<span className="tw-flex">
|
<span className="tw-flex">
|
||||||
<ProfilePicture
|
<UserPopOverCard userName={taskDetail.createdBy || ''}>
|
||||||
displayName={taskDetail.createdBy || ''}
|
<span className="tw-flex">
|
||||||
id=""
|
<ProfilePicture
|
||||||
name={taskDetail.createdBy || ''}
|
displayName={taskDetail.createdBy || ''}
|
||||||
width="20"
|
id=""
|
||||||
/>
|
name={taskDetail.createdBy || ''}
|
||||||
<span className="tw-font-semibold tw-cursor-pointer hover:tw-underline tw-ml-1">
|
width="20"
|
||||||
{taskDetail.createdBy}
|
/>
|
||||||
|
<span className="tw-font-semibold tw-cursor-pointer hover:tw-underline tw-ml-1">
|
||||||
|
{taskDetail.createdBy}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</UserPopOverCard>
|
||||||
|
<span className="tw-ml-1">created this task </span>
|
||||||
|
<span className="tw-ml-1">
|
||||||
|
{toLower(
|
||||||
|
getDayTimeByTimeStamp(taskDetail.threadTs ?? 0)
|
||||||
|
)}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</UserPopOverCard>
|
</div>
|
||||||
<span className="tw-ml-1">created this task </span>
|
|
||||||
<span className="tw-ml-1">
|
|
||||||
{toLower(
|
|
||||||
getDayTimeByTimeStamp(taskDetail.threadTs as number)
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ColumnDetail column={columnObject} />
|
<ColumnDetail column={columnObject} />
|
||||||
<div className="tw-flex" data-testid="task-assignees">
|
<div className="tw-flex" data-testid="task-assignees">
|
||||||
<span
|
<span
|
||||||
className={classNames('tw-text-grey-muted', {
|
className={classNames('tw-text-grey-muted', {
|
||||||
'tw-self-center tw-mr-2': editAssignee,
|
'tw-self-center tw-mr-2': editAssignee,
|
||||||
})}>
|
})}>
|
||||||
Assignees:
|
Assignees:
|
||||||
</span>
|
</span>
|
||||||
{editAssignee ? (
|
{editAssignee ? (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<Assignees
|
<Assignees
|
||||||
assignees={assignees}
|
assignees={assignees}
|
||||||
options={options}
|
options={options}
|
||||||
onChange={setAssignees}
|
onChange={setAssignees}
|
||||||
onSearch={onSearch}
|
onSearch={onSearch}
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
className="tw-mx-1 tw-self-center ant-btn-primary-custom"
|
|
||||||
size="small"
|
|
||||||
type="primary"
|
|
||||||
onClick={() => setEditAssignee(false)}>
|
|
||||||
<FontAwesomeIcon
|
|
||||||
className="tw-w-3.5 tw-h-3.5"
|
|
||||||
icon="times"
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
className="tw-mx-1 tw-self-center ant-btn-primary-custom"
|
|
||||||
disabled={!assignees.length}
|
|
||||||
size="small"
|
|
||||||
type="primary"
|
|
||||||
onClick={onTaskUpdate}>
|
|
||||||
<FontAwesomeIcon
|
|
||||||
className="tw-w-3.5 tw-h-3.5"
|
|
||||||
icon="check"
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
</Fragment>
|
|
||||||
) : (
|
|
||||||
<Fragment>
|
|
||||||
<AssigneeList
|
|
||||||
assignees={taskDetail?.task?.assignees || []}
|
|
||||||
className="tw-ml-0.5 tw-align-middle tw-inline-flex tw-flex-wrap"
|
|
||||||
/>
|
|
||||||
{(hasEditAccess() || isCreator) && !isTaskClosed && (
|
|
||||||
<button
|
|
||||||
className="focus:tw-outline-none tw-self-baseline tw-flex-none"
|
|
||||||
data-testid="edit-suggestion"
|
|
||||||
onClick={() => setEditAssignee(true)}>
|
|
||||||
<SVGIcons
|
|
||||||
alt="edit"
|
|
||||||
icon="icon-edit"
|
|
||||||
title="Edit"
|
|
||||||
width="14px"
|
|
||||||
/>
|
/>
|
||||||
</button>
|
<Button
|
||||||
)}
|
className="tw-mx-1 tw-self-center ant-btn-primary-custom"
|
||||||
</Fragment>
|
size="small"
|
||||||
)}
|
type="primary"
|
||||||
</div>
|
onClick={() => setEditAssignee(false)}>
|
||||||
</Card>
|
|
||||||
|
|
||||||
<Card
|
|
||||||
data-testid="task-data"
|
|
||||||
style={{ ...cardStyles, marginTop: '16px', marginLeft: '24px' }}>
|
|
||||||
{isTaskDescription && (
|
|
||||||
<DescriptionTask
|
|
||||||
currentDescription={currentDescription()}
|
|
||||||
hasEditAccess={hasEditAccess()}
|
|
||||||
isTaskActionEdit={isTaskActionEdit}
|
|
||||||
suggestion={suggestion}
|
|
||||||
taskDetail={taskDetail}
|
|
||||||
onSuggestionChange={onSuggestionChange}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isTaskTags && (
|
|
||||||
<TagsTask
|
|
||||||
currentTags={getCurrentTags()}
|
|
||||||
hasEditAccess={hasEditAccess()}
|
|
||||||
isTaskActionEdit={isTaskActionEdit}
|
|
||||||
setSuggestion={setTagsSuggestion}
|
|
||||||
suggestions={tagsSuggestion}
|
|
||||||
task={taskDetail.task}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div
|
|
||||||
className="tw-flex tw-justify-end"
|
|
||||||
data-testid="task-cta-buttons">
|
|
||||||
{(hasEditAccess() || isCreator) && !isTaskClosed && (
|
|
||||||
<Button
|
|
||||||
className="ant-btn-link-custom"
|
|
||||||
type="link"
|
|
||||||
onClick={() => setModalVisible(true)}>
|
|
||||||
Close with comment
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{hasEditAccess() && !isTaskClosed && (
|
|
||||||
<Fragment>
|
|
||||||
{taskDetail.task?.suggestion ? (
|
|
||||||
<Dropdown.Button
|
|
||||||
className="ant-btn-primary-dropdown"
|
|
||||||
icon={
|
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
className="tw-text-sm"
|
className="tw-w-3.5 tw-h-3.5"
|
||||||
icon={faChevronDown}
|
icon="times"
|
||||||
/>
|
/>
|
||||||
}
|
</Button>
|
||||||
overlay={
|
<Button
|
||||||
<Menu
|
className="tw-mx-1 tw-self-center ant-btn-primary-custom"
|
||||||
selectable
|
disabled={!assignees.length}
|
||||||
items={TASK_ACTION_LIST}
|
size="small"
|
||||||
selectedKeys={[taskAction.key]}
|
type="primary"
|
||||||
onClick={(info) => onTaskActionChange(info.key)}
|
onClick={onTaskUpdate}>
|
||||||
|
<FontAwesomeIcon
|
||||||
|
className="tw-w-3.5 tw-h-3.5"
|
||||||
|
icon="check"
|
||||||
/>
|
/>
|
||||||
}
|
</Button>
|
||||||
trigger={['click']}
|
</Fragment>
|
||||||
type="primary"
|
|
||||||
onClick={onTaskResolve}>
|
|
||||||
{taskAction.label}
|
|
||||||
</Dropdown.Button>
|
|
||||||
) : (
|
) : (
|
||||||
|
<Fragment>
|
||||||
|
<AssigneeList
|
||||||
|
assignees={taskDetail?.task?.assignees || []}
|
||||||
|
className="tw-ml-0.5 tw-align-middle tw-inline-flex tw-flex-wrap"
|
||||||
|
/>
|
||||||
|
{(hasEditAccess() || isCreator) && !isTaskClosed && (
|
||||||
|
<button
|
||||||
|
className="focus:tw-outline-none tw-self-baseline tw-flex-none"
|
||||||
|
data-testid="edit-suggestion"
|
||||||
|
onClick={() => setEditAssignee(true)}>
|
||||||
|
<SVGIcons
|
||||||
|
alt="edit"
|
||||||
|
icon="icon-edit"
|
||||||
|
title="Edit"
|
||||||
|
width="14px"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</Fragment>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
<Card
|
||||||
|
className="mt-4 ml-6"
|
||||||
|
data-testid="task-data"
|
||||||
|
style={{
|
||||||
|
...cardStyles,
|
||||||
|
}}>
|
||||||
|
{isTaskDescription && (
|
||||||
|
<DescriptionTask
|
||||||
|
currentDescription={currentDescription()}
|
||||||
|
hasEditAccess={hasEditAccess()}
|
||||||
|
isTaskActionEdit={isTaskActionEdit}
|
||||||
|
suggestion={suggestion}
|
||||||
|
taskDetail={taskDetail}
|
||||||
|
onSuggestionChange={onSuggestionChange}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isTaskTags && (
|
||||||
|
<TagsTask
|
||||||
|
currentTags={getCurrentTags()}
|
||||||
|
hasEditAccess={hasEditAccess()}
|
||||||
|
isTaskActionEdit={isTaskActionEdit}
|
||||||
|
setSuggestion={setTagsSuggestion}
|
||||||
|
suggestions={tagsSuggestion}
|
||||||
|
task={taskDetail.task}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div
|
||||||
|
className="tw-flex tw-justify-end"
|
||||||
|
data-testid="task-cta-buttons">
|
||||||
|
{(hasEditAccess() || isCreator) && !isTaskClosed && (
|
||||||
<Button
|
<Button
|
||||||
className="ant-btn-primary-custom"
|
className="ant-btn-link-custom"
|
||||||
disabled={!suggestion}
|
type="link"
|
||||||
type="primary"
|
onClick={() => setModalVisible(true)}>
|
||||||
onClick={onTaskResolve}>
|
Close with comment
|
||||||
Add Description
|
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</Fragment>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{isTaskClosed && <ClosedTask task={taskDetail.task} />}
|
{hasEditAccess() && !isTaskClosed && (
|
||||||
</Card>
|
<Fragment>
|
||||||
<CommentModal
|
{taskDetail.task?.suggestion ? (
|
||||||
comment={comment}
|
<Dropdown.Button
|
||||||
isVisible={modalVisible}
|
className="ant-btn-primary-dropdown"
|
||||||
setComment={setComment}
|
data-testid="complete-task"
|
||||||
taskDetail={taskDetail}
|
icon={
|
||||||
onClose={onCommentModalClose}
|
<FontAwesomeIcon
|
||||||
onConfirm={onTaskReject}
|
className="tw-text-sm"
|
||||||
/>
|
icon={faChevronDown}
|
||||||
</Content>
|
/>
|
||||||
|
}
|
||||||
<Sider
|
overlay={
|
||||||
className="ant-layout-sider-task-detail"
|
<Menu
|
||||||
data-testid="task-right-sider"
|
selectable
|
||||||
theme="light"
|
items={TASK_ACTION_LIST}
|
||||||
width={600}>
|
selectedKeys={[taskAction.key]}
|
||||||
<Tabs className="ant-tabs-custom-line" onChange={onTabChange}>
|
onClick={(info) => onTaskActionChange(info.key)}
|
||||||
<TabPane key={PanelTab.TASKS} tab="Task">
|
/>
|
||||||
{!isEmpty(taskFeedDetail) ? (
|
}
|
||||||
<div id="task-feed">
|
trigger={['click']}
|
||||||
<FeedPanelBody
|
type="primary"
|
||||||
isLoading={isLoading}
|
onClick={onTaskResolve}>
|
||||||
threadData={taskFeedDetail}
|
{taskAction.label}
|
||||||
updateThreadHandler={onTaskFeedUpdate}
|
</Dropdown.Button>
|
||||||
/>
|
) : (
|
||||||
<ActivityFeedEditor
|
<Button
|
||||||
buttonClass="tw-mr-4"
|
className="ant-btn-primary-custom"
|
||||||
className="tw-ml-5 tw-mr-2 tw-mb-2"
|
disabled={!suggestion}
|
||||||
onSave={onPostTaskFeed}
|
type="primary"
|
||||||
/>
|
onClick={onTaskResolve}>
|
||||||
|
Add Description
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Fragment>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
|
||||||
</TabPane>
|
|
||||||
|
|
||||||
<TabPane key={PanelTab.CONVERSATIONS} tab="Conversations">
|
{isTaskClosed && <ClosedTask task={taskDetail.task} />}
|
||||||
{!isEmpty(taskFeedDetail) ? (
|
</Card>
|
||||||
<ActivityThreadPanelBody
|
<CommentModal
|
||||||
className="tw-p-0"
|
comment={comment}
|
||||||
createThread={createThread}
|
isVisible={modalVisible}
|
||||||
deletePostHandler={deletePostHandler}
|
setComment={setComment}
|
||||||
postFeedHandler={postFeedHandler}
|
taskDetail={taskDetail}
|
||||||
showHeader={false}
|
onClose={onCommentModalClose}
|
||||||
threadLink={taskFeedDetail.about}
|
onConfirm={onTaskReject}
|
||||||
threadType={ThreadType.Conversation}
|
/>
|
||||||
updateThreadHandler={updateThreadHandler}
|
</Content>
|
||||||
/>
|
|
||||||
) : null}
|
<Sider
|
||||||
</TabPane>
|
className="ant-layout-sider-task-detail"
|
||||||
</Tabs>
|
data-testid="task-right-sider"
|
||||||
</Sider>
|
theme="light"
|
||||||
</Fragment>
|
width={600}>
|
||||||
|
<Tabs className="ant-tabs-custom-line" onChange={onTabChange}>
|
||||||
|
<TabPane key={PanelTab.TASKS} tab="Task">
|
||||||
|
{!isEmpty(taskFeedDetail) ? (
|
||||||
|
<div id="task-feed">
|
||||||
|
<FeedPanelBody
|
||||||
|
isLoading={isLoading}
|
||||||
|
threadData={taskFeedDetail}
|
||||||
|
updateThreadHandler={onTaskFeedUpdate}
|
||||||
|
/>
|
||||||
|
<ActivityFeedEditor
|
||||||
|
buttonClass="tw-mr-4"
|
||||||
|
className="tw-ml-5 tw-mr-2 tw-mb-2"
|
||||||
|
onSave={onPostTaskFeed}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</TabPane>
|
||||||
|
|
||||||
|
<TabPane key={PanelTab.CONVERSATIONS} tab="Conversations">
|
||||||
|
{!isEmpty(taskFeedDetail) ? (
|
||||||
|
<ActivityThreadPanelBody
|
||||||
|
className="tw-p-0"
|
||||||
|
createThread={createThread}
|
||||||
|
deletePostHandler={deletePostHandler}
|
||||||
|
postFeedHandler={postFeedHandler}
|
||||||
|
showHeader={false}
|
||||||
|
threadLink={taskFeedDetail.about}
|
||||||
|
threadType={ThreadType.Conversation}
|
||||||
|
updateThreadHandler={updateThreadHandler}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</TabPane>
|
||||||
|
</Tabs>
|
||||||
|
</Sider>
|
||||||
|
</Fragment>
|
||||||
|
)}
|
||||||
|
</Layout>
|
||||||
)}
|
)}
|
||||||
</Layout>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -12,11 +12,12 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Dashboard } from '../../generated/entity/data/dashboard';
|
import { Dashboard } from '../../generated/entity/data/dashboard';
|
||||||
|
import { Mlmodel } from '../../generated/entity/data/mlmodel';
|
||||||
import { Pipeline } from '../../generated/entity/data/pipeline';
|
import { Pipeline } from '../../generated/entity/data/pipeline';
|
||||||
import { Table } from '../../generated/entity/data/table';
|
import { Table } from '../../generated/entity/data/table';
|
||||||
import { Topic } from '../../generated/entity/data/topic';
|
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 {
|
export interface Option {
|
||||||
label: string;
|
label: string;
|
||||||
|
|||||||
@ -45,3 +45,11 @@ window.ResizeObserver = jest.fn().mockImplementation(() => ({
|
|||||||
unobserve: jest.fn(),
|
unobserve: jest.fn(),
|
||||||
disconnect: jest.fn(),
|
disconnect: jest.fn(),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mock implementation of IntersectionObserver
|
||||||
|
*/
|
||||||
|
window.IntersectionObserver = jest.fn().mockImplementation(() => ({
|
||||||
|
observe: jest.fn(),
|
||||||
|
unobserve: jest.fn(),
|
||||||
|
}));
|
||||||
|
|||||||
@ -192,6 +192,12 @@
|
|||||||
.mt-4 {
|
.mt-4 {
|
||||||
margin-top: 1rem;
|
margin-top: 1rem;
|
||||||
}
|
}
|
||||||
|
.ml-4 {
|
||||||
|
margin-left: 1rem;
|
||||||
|
}
|
||||||
|
.ml-6 {
|
||||||
|
margin-left: 1.5rem;
|
||||||
|
}
|
||||||
.mt-8 {
|
.mt-8 {
|
||||||
margin-top: 2rem;
|
margin-top: 2rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@ export const ANNOUNCEMENT_ENTITIES = [
|
|||||||
EntityType.DASHBOARD,
|
EntityType.DASHBOARD,
|
||||||
EntityType.TOPIC,
|
EntityType.TOPIC,
|
||||||
EntityType.PIPELINE,
|
EntityType.PIPELINE,
|
||||||
|
EntityType.MLMODEL,
|
||||||
];
|
];
|
||||||
|
|
||||||
export const validateMessages = {
|
export const validateMessages = {
|
||||||
|
|||||||
@ -21,6 +21,11 @@ export const mlModelTabs = [
|
|||||||
name: 'Features',
|
name: 'Features',
|
||||||
path: 'features',
|
path: 'features',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'Activity Feed',
|
||||||
|
path: 'activity_feed',
|
||||||
|
field: TabSpecificField.ACTIVITY_FEED,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: 'Details',
|
name: 'Details',
|
||||||
path: 'details',
|
path: 'details',
|
||||||
@ -39,17 +44,21 @@ export const mlModelTabs = [
|
|||||||
export const getCurrentMlModelTab = (tab: string) => {
|
export const getCurrentMlModelTab = (tab: string) => {
|
||||||
let currentTab = 1;
|
let currentTab = 1;
|
||||||
switch (tab) {
|
switch (tab) {
|
||||||
case 'details':
|
case 'activity_feed':
|
||||||
currentTab = 2;
|
currentTab = 2;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case 'lineage':
|
case 'details':
|
||||||
currentTab = 3;
|
currentTab = 3;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case 'custom_properties':
|
case 'lineage':
|
||||||
currentTab = 4;
|
currentTab = 4;
|
||||||
|
|
||||||
|
break;
|
||||||
|
case 'custom_properties':
|
||||||
|
currentTab = 5;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'features':
|
case 'features':
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import { Change, diffWordsWithSpace } from 'diff';
|
|||||||
import { isEqual, isUndefined } from 'lodash';
|
import { isEqual, isUndefined } from 'lodash';
|
||||||
import { getDashboardByFqn } from '../axiosAPIs/dashboardAPI';
|
import { getDashboardByFqn } from '../axiosAPIs/dashboardAPI';
|
||||||
import { getUserSuggestions } from '../axiosAPIs/miscAPI';
|
import { getUserSuggestions } from '../axiosAPIs/miscAPI';
|
||||||
|
import { getMlModelByFQN } from '../axiosAPIs/mlModelAPI';
|
||||||
import { getPipelineByFqn } from '../axiosAPIs/pipelineAPI';
|
import { getPipelineByFqn } from '../axiosAPIs/pipelineAPI';
|
||||||
import { getTableDetailsByFQN } from '../axiosAPIs/tableAPI';
|
import { getTableDetailsByFQN } from '../axiosAPIs/tableAPI';
|
||||||
import { getTopicByFqn } from '../axiosAPIs/topicsAPI';
|
import { getTopicByFqn } from '../axiosAPIs/topicsAPI';
|
||||||
@ -40,6 +41,7 @@ import {
|
|||||||
import { getEntityName, getPartialNameFromTableFQN } from './CommonUtils';
|
import { getEntityName, getPartialNameFromTableFQN } from './CommonUtils';
|
||||||
import { defaultFields as DashboardFields } from './DashboardDetailsUtils';
|
import { defaultFields as DashboardFields } from './DashboardDetailsUtils';
|
||||||
import { defaultFields as TableFields } from './DatasetDetailsUtils';
|
import { defaultFields as TableFields } from './DatasetDetailsUtils';
|
||||||
|
import { defaultFields as MlModelFields } from './MlModelDetailsUtils';
|
||||||
import { defaultFields as PipelineFields } from './PipelineDetailsUtils';
|
import { defaultFields as PipelineFields } from './PipelineDetailsUtils';
|
||||||
import { serviceTypeLogo } from './ServiceUtils';
|
import { serviceTypeLogo } from './ServiceUtils';
|
||||||
import { getEntityLink } from './TableUtils';
|
import { getEntityLink } from './TableUtils';
|
||||||
@ -183,6 +185,7 @@ export const TASK_ENTITIES = [
|
|||||||
EntityType.DASHBOARD,
|
EntityType.DASHBOARD,
|
||||||
EntityType.TOPIC,
|
EntityType.TOPIC,
|
||||||
EntityType.PIPELINE,
|
EntityType.PIPELINE,
|
||||||
|
EntityType.MLMODEL,
|
||||||
];
|
];
|
||||||
|
|
||||||
export const getBreadCrumbList = (
|
export const getBreadCrumbList = (
|
||||||
@ -248,6 +251,10 @@ export const getBreadCrumbList = (
|
|||||||
return [service(ServiceCategory.PIPELINE_SERVICES), activeEntity];
|
return [service(ServiceCategory.PIPELINE_SERVICES), activeEntity];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case EntityType.MLMODEL: {
|
||||||
|
return [service(ServiceCategory.ML_MODEL_SERVICES), activeEntity];
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@ -291,6 +298,14 @@ export const fetchEntityDetail = (
|
|||||||
.catch((err: AxiosError) => showErrorToast(err));
|
.catch((err: AxiosError) => showErrorToast(err));
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
case EntityType.MLMODEL:
|
||||||
|
getMlModelByFQN(entityFQN, MlModelFields)
|
||||||
|
.then((res) => {
|
||||||
|
setEntityData(res);
|
||||||
|
})
|
||||||
|
.catch((err: AxiosError) => showErrorToast(err));
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user