mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-09-26 01:15:08 +00:00
parent
a3ca37e6a8
commit
09b37d28f2
@ -66,8 +66,8 @@ import {
|
||||
getColumnType,
|
||||
getDataLabel,
|
||||
getDeletedLineagePlaceholder,
|
||||
getLayoutedElementsV1,
|
||||
getLineageDataV1,
|
||||
getLayoutedElements,
|
||||
getLineageData,
|
||||
getModalBodyText,
|
||||
getNodeRemoveButton,
|
||||
getUniqueFlowElements,
|
||||
@ -461,7 +461,7 @@ const Entitylineage: FunctionComponent<EntityLineageProp> = ({
|
||||
setConfirmDelete(false);
|
||||
};
|
||||
|
||||
const setElementsHandleV1 = (data: EntityLineage) => {
|
||||
const setElementsHandle = (data: EntityLineage) => {
|
||||
let uniqueElements: CustomeElement = {
|
||||
node: [],
|
||||
edge: [],
|
||||
@ -471,7 +471,7 @@ const Entitylineage: FunctionComponent<EntityLineageProp> = ({
|
||||
edges: [...(edges || [])],
|
||||
};
|
||||
if (!isEmpty(data)) {
|
||||
const graphElements = getLineageDataV1(
|
||||
const graphElements = getLineageData(
|
||||
data,
|
||||
selectNodeHandler,
|
||||
loadNodeHandler,
|
||||
@ -491,7 +491,7 @@ const Entitylineage: FunctionComponent<EntityLineageProp> = ({
|
||||
node: getUniqueFlowElements(graphElements.node) as Node[],
|
||||
edge: getUniqueFlowElements(graphElements.edge) as Edge[],
|
||||
};
|
||||
const { node, edge } = getLayoutedElementsV1(uniqueElements);
|
||||
const { node, edge } = getLayoutedElements(uniqueElements);
|
||||
setNodes(node);
|
||||
setEdges(edge);
|
||||
|
||||
@ -1288,7 +1288,7 @@ const Entitylineage: FunctionComponent<EntityLineageProp> = ({
|
||||
|
||||
useEffect(() => {
|
||||
if (!deleted && !isEmpty(updatedLineageData)) {
|
||||
setElementsHandleV1(updatedLineageData);
|
||||
setElementsHandle(updatedLineageData);
|
||||
}
|
||||
}, [isNodeLoading, isEditMode]);
|
||||
|
||||
@ -1322,9 +1322,13 @@ const Entitylineage: FunctionComponent<EntityLineageProp> = ({
|
||||
}, [selectedEdge, confirmDelete]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isEmpty(entityLineage) && !deleted) {
|
||||
if (
|
||||
!isEmpty(entityLineage) &&
|
||||
!isUndefined(entityLineage.entity) &&
|
||||
!deleted
|
||||
) {
|
||||
setUpdatedLineageData(entityLineage);
|
||||
setElementsHandleV1(entityLineage);
|
||||
setElementsHandle(entityLineage);
|
||||
}
|
||||
}, [entityLineage]);
|
||||
|
||||
|
@ -161,8 +161,8 @@ jest.mock('../../utils/EntityLineageUtils', () => ({
|
||||
<p>Lineage data is not available for deleted entities.</p>
|
||||
),
|
||||
getHeaderLabel: jest.fn().mockReturnValue(<p>Header label</p>),
|
||||
getLayoutedElementsV1: jest.fn().mockImplementation(() => mockFlowData),
|
||||
getLineageDataV1: jest.fn().mockImplementation(() => mockFlowData),
|
||||
getLayoutedElements: jest.fn().mockImplementation(() => mockFlowData),
|
||||
getLineageData: jest.fn().mockImplementation(() => mockFlowData),
|
||||
getModalBodyText: jest.fn(),
|
||||
onLoad: jest.fn(),
|
||||
onNodeContextMenu: jest.fn(),
|
||||
|
@ -12,6 +12,7 @@
|
||||
*/
|
||||
|
||||
import { findByTestId, findByText, render } from '@testing-library/react';
|
||||
import { LeafNodes } from 'Models';
|
||||
import React from 'react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { Mlmodel } from '../../generated/entity/data/mlmodel';
|
||||
@ -141,6 +142,16 @@ const mockProp = {
|
||||
tagUpdateHandler,
|
||||
updateMlModelFeatures,
|
||||
settingsUpdateHandler,
|
||||
lineageTabData: {
|
||||
loadNodeHandler: jest.fn(),
|
||||
addLineageHandler: jest.fn(),
|
||||
removeLineageHandler: jest.fn(),
|
||||
entityLineageHandler: jest.fn(),
|
||||
isLineageLoading: false,
|
||||
entityLineage: { entity: { id: 'test', type: 'mlmodel' } },
|
||||
lineageLeafNodes: {} as LeafNodes,
|
||||
isNodeLoading: { id: undefined, state: false },
|
||||
},
|
||||
};
|
||||
|
||||
jest.mock('../ManageTab/ManageTab.component', () => {
|
||||
@ -159,6 +170,10 @@ jest.mock('../common/rich-text-editor/RichTextEditorPreviewer', () => {
|
||||
return jest.fn().mockReturnValue(<p>RichTextEditorPreviewer</p>);
|
||||
});
|
||||
|
||||
jest.mock('../EntityLineage/EntityLineage.component', () => {
|
||||
return jest.fn().mockReturnValue(<p>EntityLineage.component</p>);
|
||||
});
|
||||
|
||||
jest.mock('./MlModelFeaturesList', () => {
|
||||
return jest.fn().mockReturnValue(<p>MlModelFeaturesList</p>);
|
||||
});
|
||||
@ -224,7 +239,7 @@ describe('Test MlModel entity detail component', () => {
|
||||
expect(mlStoreTable).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Should render manage component for manage tab', async () => {
|
||||
it('Should render lineage tab', async () => {
|
||||
const { container } = render(
|
||||
<MlModelDetailComponent {...mockProp} activeTab={3} />,
|
||||
{
|
||||
@ -232,6 +247,19 @@ describe('Test MlModel entity detail component', () => {
|
||||
}
|
||||
);
|
||||
|
||||
const detailContainer = await findByTestId(container, 'lineage-details');
|
||||
|
||||
expect(detailContainer).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Should render manage component for manage tab', async () => {
|
||||
const { container } = render(
|
||||
<MlModelDetailComponent {...mockProp} activeTab={4} />,
|
||||
{
|
||||
wrapper: MemoryRouter,
|
||||
}
|
||||
);
|
||||
|
||||
const detailContainer = await findByTestId(container, 'mlmodel-details');
|
||||
const manageTab = await findByTestId(container, 'manage');
|
||||
|
||||
|
@ -14,7 +14,13 @@
|
||||
import classNames from 'classnames';
|
||||
import { startCase, uniqueId } from 'lodash';
|
||||
import { observer } from 'mobx-react';
|
||||
import { EntityTags, ExtraInfo } from 'Models';
|
||||
import {
|
||||
EntityTags,
|
||||
ExtraInfo,
|
||||
LeafNodes,
|
||||
LineagePos,
|
||||
LoadingNodeState,
|
||||
} from 'Models';
|
||||
import React, {
|
||||
FC,
|
||||
Fragment,
|
||||
@ -33,6 +39,7 @@ import { EntityType } from '../../enums/entity.enum';
|
||||
import { ServiceCategory } from '../../enums/service.enum';
|
||||
import { OwnerType } from '../../enums/user.enum';
|
||||
import { Mlmodel } from '../../generated/entity/data/mlmodel';
|
||||
import { EntityLineage } from '../../generated/type/entityLineage';
|
||||
import { EntityReference } from '../../generated/type/entityReference';
|
||||
import { LabelType, State, TagLabel } from '../../generated/type/tagLabel';
|
||||
import { getEntityName, getEntityPlaceHolder } from '../../utils/CommonUtils';
|
||||
@ -43,6 +50,8 @@ import EntityPageInfo from '../common/entityPageInfo/EntityPageInfo';
|
||||
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 ManageTabComponent from '../ManageTab/ManageTab.component';
|
||||
import MlModelFeaturesList from './MlModelFeaturesList';
|
||||
|
||||
@ -56,6 +65,16 @@ interface MlModelDetailProp extends HTMLAttributes<HTMLDivElement> {
|
||||
tagUpdateHandler: (updatedMlModel: Mlmodel) => void;
|
||||
updateMlModelFeatures: (updatedMlModel: Mlmodel) => 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;
|
||||
};
|
||||
}
|
||||
|
||||
const MlModelDetail: FC<MlModelDetailProp> = ({
|
||||
@ -68,6 +87,7 @@ const MlModelDetail: FC<MlModelDetailProp> = ({
|
||||
tagUpdateHandler,
|
||||
settingsUpdateHandler,
|
||||
updateMlModelFeatures,
|
||||
lineageTabData,
|
||||
}) => {
|
||||
const [followersCount, setFollowersCount] = useState<number>(0);
|
||||
const [isFollowing, setIsFollowing] = useState<boolean>(false);
|
||||
@ -184,6 +204,11 @@ const MlModelDetail: FC<MlModelDetailProp> = ({
|
||||
isProtected: false,
|
||||
position: 2,
|
||||
},
|
||||
{
|
||||
name: 'Lineage',
|
||||
isProtected: false,
|
||||
position: 3,
|
||||
},
|
||||
{
|
||||
name: 'Manage',
|
||||
icon: {
|
||||
@ -194,7 +219,7 @@ const MlModelDetail: FC<MlModelDetailProp> = ({
|
||||
},
|
||||
isProtected: false,
|
||||
protectedState: !mlModelDetail.owner || hasEditAccess(),
|
||||
position: 3,
|
||||
position: 4,
|
||||
},
|
||||
];
|
||||
|
||||
@ -452,6 +477,24 @@ const MlModelDetail: FC<MlModelDetailProp> = ({
|
||||
</div>
|
||||
)}
|
||||
{activeTab === 3 && (
|
||||
<div
|
||||
className="tw-px-2 tw-h-full"
|
||||
data-testid="lineage-details">
|
||||
<EntityLineageComponent
|
||||
addLineageHandler={lineageTabData.addLineageHandler}
|
||||
deleted={mlModelDetail.deleted}
|
||||
entityLineage={lineageTabData.entityLineage}
|
||||
entityLineageHandler={lineageTabData.entityLineageHandler}
|
||||
isLoading={lineageTabData.isLineageLoading}
|
||||
isNodeLoading={lineageTabData.isNodeLoading}
|
||||
isOwner={hasEditAccess()}
|
||||
lineageLeafNodes={lineageTabData.lineageLeafNodes}
|
||||
loadNodeHandler={lineageTabData.loadNodeHandler}
|
||||
removeLineageHandler={lineageTabData.removeLineageHandler}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{activeTab === 4 && (
|
||||
<div>
|
||||
<ManageTabComponent
|
||||
allowDelete
|
||||
|
@ -93,11 +93,11 @@ jest.mock('../../utils/EntityLineageUtils', () => ({
|
||||
.fn()
|
||||
.mockReturnValue(<p>Task data is not available for deleted entities.</p>),
|
||||
getHeaderLabel: jest.fn().mockReturnValue(<p>Header label</p>),
|
||||
getLayoutedElementsV1: jest.fn().mockImplementation(() => ({
|
||||
getLayoutedElements: jest.fn().mockImplementation(() => ({
|
||||
node: mockNodes,
|
||||
edge: mockEdges,
|
||||
})),
|
||||
getLineageDataV1: jest.fn().mockReturnValue([]),
|
||||
getLineageData: jest.fn().mockReturnValue([]),
|
||||
getModalBodyText: jest.fn(),
|
||||
onLoad: jest.fn(),
|
||||
onNodeContextMenu: jest.fn(),
|
||||
|
@ -22,7 +22,7 @@ import ReactFlow, {
|
||||
import { PipelineStatus, Task } from '../../generated/entity/data/pipeline';
|
||||
import { EntityReference } from '../../generated/type/entityReference';
|
||||
import { getEntityName, replaceSpaceWith_ } from '../../utils/CommonUtils';
|
||||
import { getLayoutedElementsV1, onLoad } from '../../utils/EntityLineageUtils';
|
||||
import { getLayoutedElements, onLoad } from '../../utils/EntityLineageUtils';
|
||||
import { getTaskExecStatus } from '../../utils/PipelineDetailsUtils';
|
||||
import TaskNode from './TaskNode';
|
||||
|
||||
@ -92,7 +92,7 @@ const TasksDAGView = ({ tasks, selectedExec }: Props) => {
|
||||
return [...prev, ...taskEdges];
|
||||
}, [] as Edge[]);
|
||||
|
||||
const { node: nodeValue, edge: edgeValue } = getLayoutedElementsV1({
|
||||
const { node: nodeValue, edge: edgeValue } = getLayoutedElements({
|
||||
node: nodes,
|
||||
edge: edges,
|
||||
});
|
||||
|
@ -48,6 +48,7 @@ import Description from '../common/description/Description';
|
||||
import EntityPageInfo from '../common/entityPageInfo/EntityPageInfo';
|
||||
import TabsPane from '../common/TabsPane/TabsPane';
|
||||
import PageContainer from '../containers/PageContainer';
|
||||
import EntityLineageComponent from '../EntityLineage/EntityLineage.component';
|
||||
import Loader from '../Loader/Loader';
|
||||
import ManageTabComponent from '../ManageTab/ManageTab.component';
|
||||
import RequestDescriptionModal from '../Modals/RequestDescriptionModal/RequestDescriptionModal';
|
||||
@ -95,6 +96,7 @@ const TopicDetails: React.FC<TopicDetailsProps> = ({
|
||||
sampleData,
|
||||
updateThreadHandler,
|
||||
entityFieldTaskCount,
|
||||
lineageTabData,
|
||||
}: TopicDetailsProps) => {
|
||||
const [isEdit, setIsEdit] = useState(false);
|
||||
const [followersCount, setFollowersCount] = useState(0);
|
||||
@ -208,6 +210,17 @@ const TopicDetails: React.FC<TopicDetailsProps> = ({
|
||||
isProtected: false,
|
||||
position: 4,
|
||||
},
|
||||
{
|
||||
name: 'Lineage',
|
||||
icon: {
|
||||
alt: 'lineage',
|
||||
name: 'icon-lineage',
|
||||
title: 'Lineage',
|
||||
selectedName: 'icon-lineagecolor',
|
||||
},
|
||||
isProtected: false,
|
||||
position: 5,
|
||||
},
|
||||
{
|
||||
name: 'Manage',
|
||||
icon: {
|
||||
@ -218,7 +231,7 @@ const TopicDetails: React.FC<TopicDetailsProps> = ({
|
||||
},
|
||||
isProtected: true,
|
||||
protectedState: !owner || hasEditAccess(),
|
||||
position: 5,
|
||||
position: 6,
|
||||
},
|
||||
];
|
||||
const extraInfo: Array<ExtraInfo> = [
|
||||
@ -485,6 +498,24 @@ const TopicDetails: React.FC<TopicDetailsProps> = ({
|
||||
</div>
|
||||
)}
|
||||
{activeTab === 5 && (
|
||||
<div
|
||||
className="tw-px-2 tw-h-full"
|
||||
data-testid="lineage-details">
|
||||
<EntityLineageComponent
|
||||
addLineageHandler={lineageTabData.addLineageHandler}
|
||||
deleted={deleted}
|
||||
entityLineage={lineageTabData.entityLineage}
|
||||
entityLineageHandler={lineageTabData.entityLineageHandler}
|
||||
isLoading={lineageTabData.isLineageLoading}
|
||||
isNodeLoading={lineageTabData.isNodeLoading}
|
||||
isOwner={hasEditAccess()}
|
||||
lineageLeafNodes={lineageTabData.lineageLeafNodes}
|
||||
loadNodeHandler={lineageTabData.loadNodeHandler}
|
||||
removeLineageHandler={lineageTabData.removeLineageHandler}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{activeTab === 6 && (
|
||||
<div>
|
||||
<ManageTabComponent
|
||||
allowDelete
|
||||
|
@ -11,16 +11,24 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { EntityFieldThreadCount, EntityTags } from 'Models';
|
||||
import {
|
||||
EntityFieldThreadCount,
|
||||
EntityTags,
|
||||
LeafNodes,
|
||||
LineagePos,
|
||||
LoadingNodeState,
|
||||
} from 'Models';
|
||||
import { FeedFilter } from '../../enums/mydata.enum';
|
||||
import { CreateThread } from '../../generated/api/feed/createThread';
|
||||
import { Topic, TopicSampleData } from '../../generated/entity/data/topic';
|
||||
import { Thread, ThreadType } from '../../generated/entity/feed/thread';
|
||||
import { EntityLineage } from '../../generated/type/entityLineage';
|
||||
import { EntityReference } from '../../generated/type/entityReference';
|
||||
import { Paging } from '../../generated/type/paging';
|
||||
import { TagLabel } from '../../generated/type/tagLabel';
|
||||
import { ThreadUpdatedFunc } from '../../interface/feed.interface';
|
||||
import { TitleBreadcrumbProps } from '../common/title-breadcrumb/title-breadcrumb.interface';
|
||||
import { Edge, EdgeData } from '../EntityLineage/EntityLineage.interface';
|
||||
|
||||
export interface TopicDetailsProps {
|
||||
topicFQN: string;
|
||||
@ -66,4 +74,14 @@ export interface TopicDetailsProps {
|
||||
postFeedHandler: (value: string, id: string) => void;
|
||||
deletePostHandler: (threadId: string, postId: string) => void;
|
||||
updateThreadHandler: ThreadUpdatedFunc;
|
||||
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;
|
||||
};
|
||||
}
|
||||
|
@ -95,6 +95,16 @@ const TopicDetailsProps = {
|
||||
paging: {} as Paging,
|
||||
fetchFeedHandler: jest.fn(),
|
||||
updateThreadHandler: jest.fn(),
|
||||
lineageTabData: {
|
||||
loadNodeHandler: jest.fn(),
|
||||
addLineageHandler: jest.fn(),
|
||||
removeLineageHandler: jest.fn(),
|
||||
entityLineageHandler: jest.fn(),
|
||||
isLineageLoading: false,
|
||||
entityLineage: { entity: { id: 'test', type: 'topic' } },
|
||||
lineageLeafNodes: {} as LeafNodes,
|
||||
isNodeLoading: { id: undefined, state: false },
|
||||
},
|
||||
};
|
||||
|
||||
const mockObserve = jest.fn();
|
||||
@ -109,6 +119,10 @@ jest.mock('../ManageTab/ManageTab.component', () => {
|
||||
return jest.fn().mockReturnValue(<p data-testid="manage">ManageTab</p>);
|
||||
});
|
||||
|
||||
jest.mock('../EntityLineage/EntityLineage.component', () => {
|
||||
return jest.fn().mockReturnValue(<p>EntityLineage.component</p>);
|
||||
});
|
||||
|
||||
jest.mock('../common/description/Description', () => {
|
||||
return jest.fn().mockReturnValue(<p>Description Component</p>);
|
||||
});
|
||||
@ -220,7 +234,7 @@ describe('Test TopicDetails component', () => {
|
||||
|
||||
it('Check if active tab is manage', async () => {
|
||||
const { container } = render(
|
||||
<TopicDetails {...TopicDetailsProps} activeTab={5} />,
|
||||
<TopicDetails {...TopicDetailsProps} activeTab={6} />,
|
||||
{
|
||||
wrapper: MemoryRouter,
|
||||
}
|
||||
@ -230,6 +244,19 @@ describe('Test TopicDetails component', () => {
|
||||
expect(manage).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Should render lineage tab', async () => {
|
||||
const { container } = render(
|
||||
<TopicDetails {...TopicDetailsProps} activeTab={5} />,
|
||||
{
|
||||
wrapper: MemoryRouter,
|
||||
}
|
||||
);
|
||||
|
||||
const detailContainer = await findByTestId(container, 'lineage-details');
|
||||
|
||||
expect(detailContainer).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Should create an observer if IntersectionObserver is available', async () => {
|
||||
const { container } = render(
|
||||
<TopicDetails {...TopicDetailsProps} activeTab={4} />,
|
||||
|
@ -12,6 +12,8 @@ export const entityData = [
|
||||
},
|
||||
{ type: EntityType.PIPELINE, label: capitalize(EntityType.PIPELINE) },
|
||||
{ type: EntityType.DASHBOARD, label: capitalize(EntityType.DASHBOARD) },
|
||||
{ type: EntityType.TOPIC, label: capitalize(EntityType.TOPIC) },
|
||||
{ type: EntityType.MLMODEL, label: capitalize(EntityType.MLMODEL) },
|
||||
];
|
||||
|
||||
export const positionX = 150;
|
||||
|
@ -15,8 +15,11 @@ import { AxiosError, AxiosResponse } from 'axios';
|
||||
import { compare } from 'fast-json-patch';
|
||||
import { isEmpty, isNil } from 'lodash';
|
||||
import { observer } from 'mobx-react';
|
||||
import { LeafNodes, LineagePos, LoadingNodeState } from 'Models';
|
||||
import React, { Fragment, useEffect, useState } from 'react';
|
||||
import { useHistory, useParams } from 'react-router-dom';
|
||||
import { getLineageByFQN } from '../../axiosAPIs/lineageAPI';
|
||||
import { addLineage, deleteLineageEdge } from '../../axiosAPIs/miscAPI';
|
||||
import {
|
||||
addFollower,
|
||||
getMlModelByFQN,
|
||||
@ -24,15 +27,25 @@ import {
|
||||
removeFollower,
|
||||
} from '../../axiosAPIs/mlModelAPI';
|
||||
import ErrorPlaceHolder from '../../components/common/error-with-placeholder/ErrorPlaceHolder';
|
||||
import {
|
||||
Edge,
|
||||
EdgeData,
|
||||
} from '../../components/EntityLineage/EntityLineage.interface';
|
||||
import Loader from '../../components/Loader/Loader';
|
||||
import MlModelDetailComponent from '../../components/MlModelDetail/MlModelDetail.component';
|
||||
import { getMlModelPath } from '../../constants/constants';
|
||||
import { EntityType, TabSpecificField } from '../../enums/entity.enum';
|
||||
import { Mlmodel } from '../../generated/entity/data/mlmodel';
|
||||
import {
|
||||
EntityLineage,
|
||||
EntityReference,
|
||||
} from '../../generated/type/entityLineage';
|
||||
import jsonData from '../../jsons/en';
|
||||
import {
|
||||
getCurrentUserId,
|
||||
getEntityMissingError,
|
||||
} from '../../utils/CommonUtils';
|
||||
import { getEntityLineage } from '../../utils/EntityUtils';
|
||||
import {
|
||||
defaultFields,
|
||||
getCurrentMlModelTab,
|
||||
@ -48,6 +61,110 @@ const MlModelPage = () => {
|
||||
const [activeTab, setActiveTab] = useState<number>(getCurrentMlModelTab(tab));
|
||||
const USERId = getCurrentUserId();
|
||||
|
||||
const [entityLineage, setEntityLineage] = useState<EntityLineage>(
|
||||
{} as EntityLineage
|
||||
);
|
||||
const [leafNodes, setLeafNodes] = useState<LeafNodes>({} as LeafNodes);
|
||||
const [isNodeLoading, setNodeLoading] = useState<LoadingNodeState>({
|
||||
id: undefined,
|
||||
state: false,
|
||||
});
|
||||
const [isLineageLoading, setIsLineageLoading] = useState<boolean>(false);
|
||||
|
||||
const getLineageData = () => {
|
||||
setIsLineageLoading(true);
|
||||
getLineageByFQN(mlModelDetail.fullyQualifiedName, EntityType.MLMODEL)
|
||||
.then((res: AxiosResponse) => {
|
||||
if (res.data) {
|
||||
setEntityLineage(res.data);
|
||||
} else {
|
||||
showErrorToast(jsonData['api-error-messages']['fetch-lineage-error']);
|
||||
}
|
||||
})
|
||||
.catch((err: AxiosError) => {
|
||||
showErrorToast(
|
||||
err,
|
||||
jsonData['api-error-messages']['fetch-lineage-error']
|
||||
);
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLineageLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const setLeafNode = (val: EntityLineage, pos: LineagePos) => {
|
||||
if (pos === 'to' && val.downstreamEdges?.length === 0) {
|
||||
setLeafNodes((prev) => ({
|
||||
...prev,
|
||||
downStreamNode: [...(prev.downStreamNode ?? []), val.entity.id],
|
||||
}));
|
||||
}
|
||||
if (pos === 'from' && val.upstreamEdges?.length === 0) {
|
||||
setLeafNodes((prev) => ({
|
||||
...prev,
|
||||
upStreamNode: [...(prev.upStreamNode ?? []), val.entity.id],
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const entityLineageHandler = (lineage: EntityLineage) => {
|
||||
setEntityLineage(lineage);
|
||||
};
|
||||
|
||||
const loadNodeHandler = (node: EntityReference, pos: LineagePos) => {
|
||||
setNodeLoading({ id: node.id, state: true });
|
||||
getLineageByFQN(node.fullyQualifiedName, node.type)
|
||||
.then((res: AxiosResponse) => {
|
||||
if (res.data) {
|
||||
setLeafNode(res.data, pos);
|
||||
setEntityLineage(getEntityLineage(entityLineage, res.data, pos));
|
||||
} else {
|
||||
showErrorToast(
|
||||
jsonData['api-error-messages']['fetch-lineage-node-error']
|
||||
);
|
||||
}
|
||||
setTimeout(() => {
|
||||
setNodeLoading((prev) => ({ ...prev, state: false }));
|
||||
}, 500);
|
||||
})
|
||||
.catch((err: AxiosError) => {
|
||||
showErrorToast(
|
||||
err,
|
||||
jsonData['api-error-messages']['fetch-lineage-node-error']
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const addLineageHandler = (edge: Edge): Promise<void> => {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
addLineage(edge)
|
||||
.then(() => {
|
||||
resolve();
|
||||
})
|
||||
.catch((err: AxiosError) => {
|
||||
showErrorToast(
|
||||
err,
|
||||
jsonData['api-error-messages']['add-lineage-error']
|
||||
);
|
||||
reject();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const removeLineageHandler = (data: EdgeData) => {
|
||||
deleteLineageEdge(
|
||||
data.fromEntity,
|
||||
data.fromId,
|
||||
data.toEntity,
|
||||
data.toId
|
||||
).catch((err: AxiosError) => {
|
||||
showErrorToast(
|
||||
err,
|
||||
jsonData['api-error-messages']['delete-lineage-error']
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const activeTabHandler = (tabValue: number) => {
|
||||
const currentTabIndex = tabValue - 1;
|
||||
if (mlModelTabs[currentTabIndex].path !== tab) {
|
||||
@ -58,6 +175,25 @@ const MlModelPage = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const fetchTabSpecificData = (tabField = '') => {
|
||||
switch (tabField) {
|
||||
case TabSpecificField.LINEAGE: {
|
||||
if (!isEmpty(mlModelDetail) && !mlModelDetail.deleted) {
|
||||
if (isEmpty(entityLineage)) {
|
||||
getLineageData();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const fetchMlModelDetails = (name: string) => {
|
||||
setIsDetailLoading(true);
|
||||
getMlModelByFQN(name, defaultFields)
|
||||
@ -227,6 +363,16 @@ const MlModelPage = () => {
|
||||
activeTab={activeTab}
|
||||
descriptionUpdateHandler={descriptionUpdateHandler}
|
||||
followMlModelHandler={followMlModel}
|
||||
lineageTabData={{
|
||||
loadNodeHandler,
|
||||
addLineageHandler,
|
||||
removeLineageHandler,
|
||||
entityLineageHandler,
|
||||
isLineageLoading,
|
||||
entityLineage,
|
||||
lineageLeafNodes: leafNodes,
|
||||
isNodeLoading,
|
||||
}}
|
||||
mlModelDetail={mlModelDetail}
|
||||
setActiveTabHandler={activeTabHandler}
|
||||
settingsUpdateHandler={settingsUpdateHandler}
|
||||
@ -244,6 +390,10 @@ const MlModelPage = () => {
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchTabSpecificData(mlModelTabs[activeTab - 1].field);
|
||||
}, [activeTab, mlModelDetail]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchMlModelDetails(mlModelFqn);
|
||||
}, [mlModelFqn]);
|
||||
|
@ -13,9 +13,15 @@
|
||||
|
||||
import { AxiosError, AxiosResponse } from 'axios';
|
||||
import { compare, Operation } from 'fast-json-patch';
|
||||
import { isUndefined } from 'lodash';
|
||||
import { isEmpty, isUndefined } from 'lodash';
|
||||
import { observer } from 'mobx-react';
|
||||
import { EntityFieldThreadCount, EntityTags } from 'Models';
|
||||
import {
|
||||
EntityFieldThreadCount,
|
||||
EntityTags,
|
||||
LeafNodes,
|
||||
LineagePos,
|
||||
LoadingNodeState,
|
||||
} from 'Models';
|
||||
import React, { FunctionComponent, useEffect, useState } from 'react';
|
||||
import { useHistory, useParams } from 'react-router-dom';
|
||||
import AppState from '../../AppState';
|
||||
@ -24,6 +30,8 @@ import {
|
||||
postFeedById,
|
||||
postThread,
|
||||
} from '../../axiosAPIs/feedsAPI';
|
||||
import { getLineageByFQN } from '../../axiosAPIs/lineageAPI';
|
||||
import { addLineage, deleteLineageEdge } from '../../axiosAPIs/miscAPI';
|
||||
import {
|
||||
addFollower,
|
||||
getTopicByFqn,
|
||||
@ -32,6 +40,10 @@ import {
|
||||
} from '../../axiosAPIs/topicsAPI';
|
||||
import ErrorPlaceHolder from '../../components/common/error-with-placeholder/ErrorPlaceHolder';
|
||||
import { TitleBreadcrumbProps } from '../../components/common/title-breadcrumb/title-breadcrumb.interface';
|
||||
import {
|
||||
Edge,
|
||||
EdgeData,
|
||||
} from '../../components/EntityLineage/EntityLineage.interface';
|
||||
import Loader from '../../components/Loader/Loader';
|
||||
import TopicDetails from '../../components/TopicDetails/TopicDetails.component';
|
||||
import {
|
||||
@ -45,6 +57,7 @@ import { ServiceCategory } from '../../enums/service.enum';
|
||||
import { CreateThread } from '../../generated/api/feed/createThread';
|
||||
import { Topic, TopicSampleData } from '../../generated/entity/data/topic';
|
||||
import { Thread, ThreadType } from '../../generated/entity/feed/thread';
|
||||
import { EntityLineage } from '../../generated/type/entityLineage';
|
||||
import { EntityReference } from '../../generated/type/entityReference';
|
||||
import { Paging } from '../../generated/type/paging';
|
||||
import { TagLabel } from '../../generated/type/tagLabel';
|
||||
@ -56,7 +69,7 @@ import {
|
||||
getEntityName,
|
||||
getFeedCounts,
|
||||
} from '../../utils/CommonUtils';
|
||||
import { getEntityFeedLink } from '../../utils/EntityUtils';
|
||||
import { getEntityFeedLink, getEntityLineage } from '../../utils/EntityUtils';
|
||||
import {
|
||||
deletePost,
|
||||
getUpdatedThread,
|
||||
@ -114,6 +127,109 @@ const TopicDetailsPage: FunctionComponent = () => {
|
||||
const [sampleData, setSampleData] = useState<TopicSampleData>();
|
||||
const [isSampleDataLoading, setIsSampleDataLoading] =
|
||||
useState<boolean>(false);
|
||||
const [entityLineage, setEntityLineage] = useState<EntityLineage>(
|
||||
{} as EntityLineage
|
||||
);
|
||||
const [leafNodes, setLeafNodes] = useState<LeafNodes>({} as LeafNodes);
|
||||
const [isNodeLoading, setNodeLoading] = useState<LoadingNodeState>({
|
||||
id: undefined,
|
||||
state: false,
|
||||
});
|
||||
const [isLineageLoading, setIsLineageLoading] = useState<boolean>(false);
|
||||
|
||||
const getLineageData = () => {
|
||||
setIsLineageLoading(true);
|
||||
getLineageByFQN(topicFQN, EntityType.TOPIC)
|
||||
.then((res: AxiosResponse) => {
|
||||
if (res.data) {
|
||||
setEntityLineage(res.data);
|
||||
} else {
|
||||
showErrorToast(jsonData['api-error-messages']['fetch-lineage-error']);
|
||||
}
|
||||
})
|
||||
.catch((err: AxiosError) => {
|
||||
showErrorToast(
|
||||
err,
|
||||
jsonData['api-error-messages']['fetch-lineage-error']
|
||||
);
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLineageLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const setLeafNode = (val: EntityLineage, pos: LineagePos) => {
|
||||
if (pos === 'to' && val.downstreamEdges?.length === 0) {
|
||||
setLeafNodes((prev) => ({
|
||||
...prev,
|
||||
downStreamNode: [...(prev.downStreamNode ?? []), val.entity.id],
|
||||
}));
|
||||
}
|
||||
if (pos === 'from' && val.upstreamEdges?.length === 0) {
|
||||
setLeafNodes((prev) => ({
|
||||
...prev,
|
||||
upStreamNode: [...(prev.upStreamNode ?? []), val.entity.id],
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const entityLineageHandler = (lineage: EntityLineage) => {
|
||||
setEntityLineage(lineage);
|
||||
};
|
||||
|
||||
const loadNodeHandler = (node: EntityReference, pos: LineagePos) => {
|
||||
setNodeLoading({ id: node.id, state: true });
|
||||
getLineageByFQN(node.fullyQualifiedName, node.type)
|
||||
.then((res: AxiosResponse) => {
|
||||
if (res.data) {
|
||||
setLeafNode(res.data, pos);
|
||||
setEntityLineage(getEntityLineage(entityLineage, res.data, pos));
|
||||
} else {
|
||||
showErrorToast(
|
||||
jsonData['api-error-messages']['fetch-lineage-node-error']
|
||||
);
|
||||
}
|
||||
setTimeout(() => {
|
||||
setNodeLoading((prev) => ({ ...prev, state: false }));
|
||||
}, 500);
|
||||
})
|
||||
.catch((err: AxiosError) => {
|
||||
showErrorToast(
|
||||
err,
|
||||
jsonData['api-error-messages']['fetch-lineage-node-error']
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const addLineageHandler = (edge: Edge): Promise<void> => {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
addLineage(edge)
|
||||
.then(() => {
|
||||
resolve();
|
||||
})
|
||||
.catch((err: AxiosError) => {
|
||||
showErrorToast(
|
||||
err,
|
||||
jsonData['api-error-messages']['add-lineage-error']
|
||||
);
|
||||
reject();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const removeLineageHandler = (data: EdgeData) => {
|
||||
deleteLineageEdge(
|
||||
data.fromEntity,
|
||||
data.fromId,
|
||||
data.toEntity,
|
||||
data.toId
|
||||
).catch((err: AxiosError) => {
|
||||
showErrorToast(
|
||||
err,
|
||||
jsonData['api-error-messages']['delete-lineage-error']
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const activeTabHandler = (tabValue: number) => {
|
||||
const currentTabIndex = tabValue - 1;
|
||||
@ -215,6 +331,18 @@ const TopicDetailsPage: FunctionComponent = () => {
|
||||
}
|
||||
}
|
||||
|
||||
case TabSpecificField.LINEAGE: {
|
||||
if (!deleted) {
|
||||
if (isEmpty(entityLineage)) {
|
||||
getLineageData();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
@ -583,6 +711,16 @@ const TopicDetailsPage: FunctionComponent = () => {
|
||||
followers={followers}
|
||||
isSampleDataLoading={isSampleDataLoading}
|
||||
isentityThreadLoading={isentityThreadLoading}
|
||||
lineageTabData={{
|
||||
loadNodeHandler,
|
||||
addLineageHandler,
|
||||
removeLineageHandler,
|
||||
entityLineageHandler,
|
||||
isLineageLoading,
|
||||
entityLineage,
|
||||
lineageLeafNodes: leafNodes,
|
||||
isNodeLoading,
|
||||
}}
|
||||
maximumMessageSize={maximumMessageSize}
|
||||
owner={owner as EntityReference}
|
||||
paging={paging}
|
||||
|
@ -154,7 +154,7 @@ export const getColumnType = (edges: Edge[], id: string) => {
|
||||
return EntityLineageNodeType.NOT_CONNECTED;
|
||||
};
|
||||
|
||||
export const getLineageDataV1 = (
|
||||
export const getLineageData = (
|
||||
entityLineage: EntityLineage,
|
||||
onSelect: (state: boolean, value: SelectedNode) => void,
|
||||
loadNodeHandler: (node: EntityReference, pos: LineagePos) => void,
|
||||
@ -425,7 +425,7 @@ export const getDeletedLineagePlaceholder = () => {
|
||||
const dagreGraph = new dagre.graphlib.Graph();
|
||||
dagreGraph.setDefaultEdgeLabel(() => ({}));
|
||||
|
||||
export const getLayoutedElementsV1 = (
|
||||
export const getLayoutedElements = (
|
||||
elements: CustomeElement,
|
||||
direction = EntityLineageDirection.LEFT_RIGHT
|
||||
) => {
|
||||
|
@ -25,6 +25,11 @@ export const mlModelTabs = [
|
||||
name: 'Details',
|
||||
path: 'details',
|
||||
},
|
||||
{
|
||||
name: 'Lineage',
|
||||
path: 'lineage',
|
||||
field: TabSpecificField.LINEAGE,
|
||||
},
|
||||
{
|
||||
name: 'Manage',
|
||||
path: 'manage',
|
||||
@ -38,11 +43,14 @@ export const getCurrentMlModelTab = (tab: string) => {
|
||||
currentTab = 2;
|
||||
|
||||
break;
|
||||
case 'manage':
|
||||
case 'lineage':
|
||||
currentTab = 3;
|
||||
|
||||
break;
|
||||
case 'manage':
|
||||
currentTab = 4;
|
||||
|
||||
break;
|
||||
case 'features':
|
||||
currentTab = 1;
|
||||
|
||||
|
@ -249,7 +249,7 @@ export const Icons = {
|
||||
EXTERNAL_LINK_GREY: 'external-link-grey',
|
||||
PROFILER: 'icon-profiler',
|
||||
PIPELINE: 'pipeline',
|
||||
MLMODAL: 'mlmodal',
|
||||
MLMODAL: 'mlmodel-grey',
|
||||
PIPELINE_GREY: 'pipeline-grey',
|
||||
DBTMODEL_GREY: 'dbtmodel-grey',
|
||||
DBTMODEL_LIGHT_GREY: 'dbtmodel-light-grey',
|
||||
|
@ -229,6 +229,11 @@ export const getEntityIcon = (indexType: string) => {
|
||||
case EntityType.DASHBOARD:
|
||||
icon = 'dashboard-grey';
|
||||
|
||||
break;
|
||||
case SearchIndex.MLMODEL:
|
||||
case EntityType.MLMODEL:
|
||||
icon = 'mlmodel-grey';
|
||||
|
||||
break;
|
||||
case SearchIndex.PIPELINE:
|
||||
case EntityType.PIPELINE:
|
||||
|
@ -32,6 +32,11 @@ export const topicDetailsTabs = [
|
||||
name: 'Config',
|
||||
path: 'config',
|
||||
},
|
||||
{
|
||||
name: 'Lineage',
|
||||
path: 'lineage',
|
||||
field: TabSpecificField.LINEAGE,
|
||||
},
|
||||
{
|
||||
name: 'Manage',
|
||||
path: 'manage',
|
||||
@ -53,9 +58,13 @@ export const getCurrentTopicTab = (tab: string) => {
|
||||
currentTab = 4;
|
||||
|
||||
break;
|
||||
case 'manage':
|
||||
case 'lineage':
|
||||
currentTab = 5;
|
||||
|
||||
break;
|
||||
case 'manage':
|
||||
currentTab = 6;
|
||||
|
||||
break;
|
||||
|
||||
case 'schema':
|
||||
|
Loading…
x
Reference in New Issue
Block a user