mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-09-27 01:45:32 +00:00
This commit is contained in:
parent
1c6046cf8e
commit
7a517cd26d
@ -11,12 +11,9 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import classNames from 'classnames';
|
|
||||||
import { compare } from 'fast-json-patch';
|
import { compare } from 'fast-json-patch';
|
||||||
import { isNil, isUndefined } from 'lodash';
|
import { EntityTags, ExtraInfo } from 'Models';
|
||||||
import { EntityFieldThreads, EntityTags, ExtraInfo } from 'Models';
|
import React, { RefObject, useEffect, useState } from 'react';
|
||||||
import React, { Fragment, RefObject, useEffect, useState } from 'react';
|
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
import AppState from '../../AppState';
|
import AppState from '../../AppState';
|
||||||
import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants';
|
import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants';
|
||||||
import { getTeamAndUserDetailsPath } from '../../constants/constants';
|
import { getTeamAndUserDetailsPath } from '../../constants/constants';
|
||||||
@ -24,7 +21,6 @@ import { observerOptions } from '../../constants/Mydata.constants';
|
|||||||
import { EntityType } from '../../enums/entity.enum';
|
import { EntityType } from '../../enums/entity.enum';
|
||||||
import { OwnerType } from '../../enums/user.enum';
|
import { OwnerType } from '../../enums/user.enum';
|
||||||
import { Pipeline, Task } from '../../generated/entity/data/pipeline';
|
import { Pipeline, Task } from '../../generated/entity/data/pipeline';
|
||||||
import { Operation } from '../../generated/entity/policies/accessControl/rule';
|
|
||||||
import { EntityReference } from '../../generated/type/entityReference';
|
import { EntityReference } from '../../generated/type/entityReference';
|
||||||
import { Paging } from '../../generated/type/paging';
|
import { Paging } from '../../generated/type/paging';
|
||||||
import { LabelType, State } from '../../generated/type/tagLabel';
|
import { LabelType, State } from '../../generated/type/tagLabel';
|
||||||
@ -33,24 +29,15 @@ import {
|
|||||||
getCurrentUserId,
|
getCurrentUserId,
|
||||||
getEntityName,
|
getEntityName,
|
||||||
getEntityPlaceHolder,
|
getEntityPlaceHolder,
|
||||||
getHtmlForNonAdminAction,
|
|
||||||
isEven,
|
|
||||||
} from '../../utils/CommonUtils';
|
} from '../../utils/CommonUtils';
|
||||||
import { getEntityFeedLink } from '../../utils/EntityUtils';
|
import { getEntityFeedLink } from '../../utils/EntityUtils';
|
||||||
import {
|
import { getDefaultValue } from '../../utils/FeedElementUtils';
|
||||||
getDefaultValue,
|
|
||||||
getFieldThreadElement,
|
|
||||||
} from '../../utils/FeedElementUtils';
|
|
||||||
import { getEntityFieldThreadCounts } from '../../utils/FeedUtils';
|
import { getEntityFieldThreadCounts } from '../../utils/FeedUtils';
|
||||||
import SVGIcons, { Icons } from '../../utils/SvgUtils';
|
|
||||||
import { getTagsWithoutTier } from '../../utils/TableUtils';
|
import { getTagsWithoutTier } from '../../utils/TableUtils';
|
||||||
import ActivityFeedList from '../ActivityFeed/ActivityFeedList/ActivityFeedList';
|
import ActivityFeedList from '../ActivityFeed/ActivityFeedList/ActivityFeedList';
|
||||||
import ActivityThreadPanel from '../ActivityFeed/ActivityThreadPanel/ActivityThreadPanel';
|
import ActivityThreadPanel from '../ActivityFeed/ActivityThreadPanel/ActivityThreadPanel';
|
||||||
import Description from '../common/description/Description';
|
import Description from '../common/description/Description';
|
||||||
import EntityPageInfo from '../common/entityPageInfo/EntityPageInfo';
|
import EntityPageInfo from '../common/entityPageInfo/EntityPageInfo';
|
||||||
import NonAdminAction from '../common/non-admin-action/NonAdminAction';
|
|
||||||
import PopOver from '../common/popover/PopOver';
|
|
||||||
import RichTextEditorPreviewer from '../common/rich-text-editor/RichTextEditorPreviewer';
|
|
||||||
import TabsPane from '../common/TabsPane/TabsPane';
|
import TabsPane from '../common/TabsPane/TabsPane';
|
||||||
import PageContainer from '../containers/PageContainer';
|
import PageContainer from '../containers/PageContainer';
|
||||||
import Entitylineage from '../EntityLineage/EntityLineage.component';
|
import Entitylineage from '../EntityLineage/EntityLineage.component';
|
||||||
@ -59,6 +46,7 @@ import ManageTabComponent from '../ManageTab/ManageTab.component';
|
|||||||
import { ModalWithMarkdownEditor } from '../Modals/ModalWithMarkdownEditor/ModalWithMarkdownEditor';
|
import { ModalWithMarkdownEditor } from '../Modals/ModalWithMarkdownEditor/ModalWithMarkdownEditor';
|
||||||
import RequestDescriptionModal from '../Modals/RequestDescriptionModal/RequestDescriptionModal';
|
import RequestDescriptionModal from '../Modals/RequestDescriptionModal/RequestDescriptionModal';
|
||||||
import PipelineStatusList from '../PipelineStatusList/PipelineStatusList.component';
|
import PipelineStatusList from '../PipelineStatusList/PipelineStatusList.component';
|
||||||
|
import TasksDAGView from '../TasksDAGView/TasksDAGView';
|
||||||
import { PipeLineDetailsProp } from './PipelineDetails.interface';
|
import { PipeLineDetailsProp } from './PipelineDetails.interface';
|
||||||
|
|
||||||
const PipelineDetails = ({
|
const PipelineDetails = ({
|
||||||
@ -252,10 +240,6 @@ const PipelineDetails = ({
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleUpdateTask = (task: Task, index: number) => {
|
|
||||||
setEditTask({ task, index });
|
|
||||||
};
|
|
||||||
|
|
||||||
const closeEditTaskModal = (): void => {
|
const closeEditTaskModal = (): void => {
|
||||||
setEditTask(undefined);
|
setEditTask(undefined);
|
||||||
};
|
};
|
||||||
@ -384,15 +368,15 @@ const PipelineDetails = ({
|
|||||||
versionHandler={versionHandler}
|
versionHandler={versionHandler}
|
||||||
onThreadLinkSelect={onThreadLinkSelect}
|
onThreadLinkSelect={onThreadLinkSelect}
|
||||||
/>
|
/>
|
||||||
<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 tw-w-full">
|
||||||
<TabsPane
|
<TabsPane
|
||||||
activeTab={activeTab}
|
activeTab={activeTab}
|
||||||
setActiveTab={setActiveTabHandler}
|
setActiveTab={setActiveTabHandler}
|
||||||
tabs={tabs}
|
tabs={tabs}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="tw-flex-grow tw-flex tw-flex-col tw--mx-6 tw-px-7 tw-py-4">
|
<div className="tw-flex-grow tw-flex tw-flex-col tw--mx-6 tw-px-7 tw-py-4 tw-w-full">
|
||||||
<div className="tw-bg-white tw-flex-grow tw-p-4 tw-shadow tw-rounded-md">
|
<div className="tw-flex-grow tw-flex tw-flex-col tw-bg-white tw-p-4 tw-shadow tw-rounded-md tw-w-full">
|
||||||
{activeTab === 1 && (
|
{activeTab === 1 && (
|
||||||
<>
|
<>
|
||||||
<div className="tw-grid tw-grid-cols-4 tw-gap-4 tw-w-full">
|
<div className="tw-grid tw-grid-cols-4 tw-gap-4 tw-w-full">
|
||||||
@ -418,135 +402,9 @@ const PipelineDetails = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="tw-table-responsive tw-my-6">
|
<div className="tw-flex-grow tw-w-full tw-h-full">
|
||||||
{tasks ? (
|
{tasks ? (
|
||||||
<table className="tw-w-full" data-testid="tasks-table">
|
<TasksDAGView tasks={tasks} />
|
||||||
<thead>
|
|
||||||
<tr className="tableHead-row">
|
|
||||||
<th className="tableHead-cell">Task Name</th>
|
|
||||||
<th className="tableHead-cell">Description</th>
|
|
||||||
<th className="tableHead-cell">Task Type</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody className="tableBody">
|
|
||||||
{tasks?.map((task, index) => (
|
|
||||||
<tr
|
|
||||||
className={classNames(
|
|
||||||
'tableBody-row',
|
|
||||||
!isEven(index + 1) ? 'odd-row' : null
|
|
||||||
)}
|
|
||||||
key={index}>
|
|
||||||
<td className="tableBody-cell">
|
|
||||||
<Link
|
|
||||||
target="_blank"
|
|
||||||
to={{ pathname: task.taskUrl }}>
|
|
||||||
<span className="tw-flex">
|
|
||||||
<span className="tw-mr-1">
|
|
||||||
{task.displayName}
|
|
||||||
</span>
|
|
||||||
<SVGIcons
|
|
||||||
alt="external-link"
|
|
||||||
className="tw-align-middle"
|
|
||||||
icon="external-link"
|
|
||||||
width="12px"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</Link>
|
|
||||||
</td>
|
|
||||||
<td className="tw-group tableBody-cell tw-relative">
|
|
||||||
<div
|
|
||||||
className="tw-cursor-pointer tw-flex"
|
|
||||||
data-testid="description">
|
|
||||||
<div>
|
|
||||||
{task.description ? (
|
|
||||||
<RichTextEditorPreviewer
|
|
||||||
markdown={task.description}
|
|
||||||
/>
|
|
||||||
) : (
|
|
||||||
<span className="tw-no-description">
|
|
||||||
No description{' '}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{!deleted && (
|
|
||||||
<Fragment>
|
|
||||||
<NonAdminAction
|
|
||||||
html={getHtmlForNonAdminAction(
|
|
||||||
Boolean(owner)
|
|
||||||
)}
|
|
||||||
isOwner={hasEditAccess()}
|
|
||||||
permission={Operation.UpdateDescription}
|
|
||||||
position="top">
|
|
||||||
<button
|
|
||||||
className="tw-self-start tw-w-8 tw-h-auto tw-opacity-0 tw-ml-1 group-hover:tw-opacity-100 focus:tw-outline-none"
|
|
||||||
onClick={() =>
|
|
||||||
handleUpdateTask(task, index)
|
|
||||||
}>
|
|
||||||
<SVGIcons
|
|
||||||
alt="edit"
|
|
||||||
icon="icon-edit"
|
|
||||||
title="Edit"
|
|
||||||
width="12px"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</NonAdminAction>
|
|
||||||
{!isNil(
|
|
||||||
getFieldThreadElement(
|
|
||||||
task.name,
|
|
||||||
'description',
|
|
||||||
getEntityFieldThreadCounts(
|
|
||||||
'tasks',
|
|
||||||
entityFieldThreadCount
|
|
||||||
) as EntityFieldThreads[],
|
|
||||||
onThreadLinkSelect
|
|
||||||
)
|
|
||||||
) &&
|
|
||||||
!isUndefined(onEntityFieldSelect) &&
|
|
||||||
!task.description ? (
|
|
||||||
<button
|
|
||||||
className="focus:tw-outline-none tw-ml-1 tw-opacity-0 group-hover:tw-opacity-100 tw--mt-2"
|
|
||||||
data-testid="request-description"
|
|
||||||
onClick={() =>
|
|
||||||
onEntityFieldSelect?.(
|
|
||||||
`tasks/${task.name}/description`
|
|
||||||
)
|
|
||||||
}>
|
|
||||||
<PopOver
|
|
||||||
position="top"
|
|
||||||
title="Request description"
|
|
||||||
trigger="mouseenter">
|
|
||||||
<SVGIcons
|
|
||||||
alt="request-description"
|
|
||||||
className="tw-mt-2.5"
|
|
||||||
icon={Icons.REQUEST}
|
|
||||||
/>
|
|
||||||
</PopOver>
|
|
||||||
</button>
|
|
||||||
) : null}
|
|
||||||
{getFieldThreadElement(
|
|
||||||
task.name,
|
|
||||||
'description',
|
|
||||||
getEntityFieldThreadCounts(
|
|
||||||
'tasks',
|
|
||||||
entityFieldThreadCount
|
|
||||||
) as EntityFieldThreads[],
|
|
||||||
onThreadLinkSelect,
|
|
||||||
EntityType.PIPELINE,
|
|
||||||
pipelineFQN,
|
|
||||||
`tasks/${task.name}/description`,
|
|
||||||
Boolean(task.description)
|
|
||||||
)}
|
|
||||||
</Fragment>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td className="tableBody-cell">
|
|
||||||
{task.taskType}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
) : (
|
) : (
|
||||||
<div className="tw-mt-4 tw-ml-4 tw-flex tw-justify-center tw-font-medium tw-items-center tw-border tw-border-main tw-rounded-md tw-p-8">
|
<div className="tw-mt-4 tw-ml-4 tw-flex tw-justify-center tw-font-medium tw-items-center tw-border tw-border-main tw-rounded-md tw-p-8">
|
||||||
<span>No task data is available</span>
|
<span>No task data is available</span>
|
||||||
|
@ -22,6 +22,15 @@ import { Paging } from '../../generated/type/paging';
|
|||||||
import { TagLabel } from '../../generated/type/tagLabel';
|
import { TagLabel } from '../../generated/type/tagLabel';
|
||||||
import PipelineDetails from './PipelineDetails.component';
|
import PipelineDetails from './PipelineDetails.component';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mock implementation of ResizeObserver
|
||||||
|
*/
|
||||||
|
window.ResizeObserver = jest.fn().mockImplementation(() => ({
|
||||||
|
observe: jest.fn(),
|
||||||
|
unobserve: jest.fn(),
|
||||||
|
disconnect: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
jest.mock('../../authentication/auth-provider/AuthProvider', () => {
|
jest.mock('../../authentication/auth-provider/AuthProvider', () => {
|
||||||
return {
|
return {
|
||||||
useAuthContext: jest.fn(() => ({
|
useAuthContext: jest.fn(() => ({
|
||||||
@ -149,6 +158,10 @@ jest.mock('../PipelineStatusList/PipelineStatusList.component', () => {
|
|||||||
.mockReturnValue(<p data-testid="pipeline-status-list">Pipeline Status</p>);
|
.mockReturnValue(<p data-testid="pipeline-status-list">Pipeline Status</p>);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
jest.mock('../TasksDAGView/TasksDAGView', () => {
|
||||||
|
return jest.fn().mockReturnValue(<p data-testid="tasks-dag">Tasks DAG</p>);
|
||||||
|
});
|
||||||
|
|
||||||
jest.mock('../../utils/CommonUtils', () => ({
|
jest.mock('../../utils/CommonUtils', () => ({
|
||||||
addToRecentViewed: jest.fn(),
|
addToRecentViewed: jest.fn(),
|
||||||
getCountBadge: jest.fn(),
|
getCountBadge: jest.fn(),
|
||||||
@ -192,7 +205,7 @@ describe('Test PipelineDetails component', () => {
|
|||||||
wrapper: MemoryRouter,
|
wrapper: MemoryRouter,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
const taskDetail = await findByTestId(container, 'tasks-table');
|
const taskDetail = await findByTestId(container, 'tasks-dag');
|
||||||
|
|
||||||
expect(taskDetail).toBeInTheDocument();
|
expect(taskDetail).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* 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 { findByTestId, render } from '@testing-library/react';
|
||||||
|
import React from 'react';
|
||||||
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
|
import TasksDAGView from './TasksDAGView';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* mock implementation of ResizeObserver
|
||||||
|
*/
|
||||||
|
window.ResizeObserver = jest.fn().mockImplementation(() => ({
|
||||||
|
observe: jest.fn(),
|
||||||
|
unobserve: jest.fn(),
|
||||||
|
disconnect: jest.fn(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('../../authentication/auth-provider/AuthProvider', () => {
|
||||||
|
return {
|
||||||
|
useAuthContext: jest.fn(() => ({
|
||||||
|
isAuthDisabled: false,
|
||||||
|
isAuthenticated: true,
|
||||||
|
isProtectedRoute: jest.fn().mockReturnValue(true),
|
||||||
|
isTourRoute: jest.fn().mockReturnValue(false),
|
||||||
|
onLogoutHandler: jest.fn(),
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const TasksDAGViewProps = {
|
||||||
|
tasks: [
|
||||||
|
{
|
||||||
|
name: 'task1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'task2',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'task3',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
jest.mock('../../utils/EntityLineageUtils', () => ({
|
||||||
|
dragHandle: jest.fn(),
|
||||||
|
getDataLabel: jest
|
||||||
|
.fn()
|
||||||
|
.mockReturnValue(<span data-testid="lineage-entity">datalabel</span>),
|
||||||
|
getDeletedLineagePlaceholder: jest
|
||||||
|
.fn()
|
||||||
|
.mockReturnValue(<p>Task data is not available for deleted entities.</p>),
|
||||||
|
getHeaderLabel: jest.fn().mockReturnValue(<p>Header label</p>),
|
||||||
|
getLayoutedElements: jest.fn().mockReturnValue([]),
|
||||||
|
getLineageData: jest.fn().mockReturnValue([]),
|
||||||
|
getModalBodyText: jest.fn(),
|
||||||
|
onLoad: jest.fn(),
|
||||||
|
onNodeContextMenu: jest.fn(),
|
||||||
|
onNodeMouseEnter: jest.fn(),
|
||||||
|
onNodeMouseLeave: jest.fn(),
|
||||||
|
onNodeMouseMove: jest.fn(),
|
||||||
|
getUniqueFlowElements: jest.fn().mockReturnValue([]),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('Test PipelineDetails component', () => {
|
||||||
|
it('Checks if the PipelineDetails component has all the proper components rendered', async () => {
|
||||||
|
const { container } = render(<TasksDAGView {...TasksDAGViewProps} />, {
|
||||||
|
wrapper: MemoryRouter,
|
||||||
|
});
|
||||||
|
const reactFlowElement = await findByTestId(
|
||||||
|
container,
|
||||||
|
'react-flow-component'
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(reactFlowElement).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,100 @@
|
|||||||
|
/*
|
||||||
|
* 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 React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
import ReactFlow, {
|
||||||
|
ArrowHeadType,
|
||||||
|
Edge,
|
||||||
|
Elements,
|
||||||
|
Node,
|
||||||
|
} from 'react-flow-renderer';
|
||||||
|
import { Task } from '../../generated/entity/data/pipeline';
|
||||||
|
import { EntityReference } from '../../generated/type/entityReference';
|
||||||
|
import { getEntityName, replaceSpaceWith_ } from '../../utils/CommonUtils';
|
||||||
|
import { getLayoutedElements, onLoad } from '../../utils/EntityLineageUtils';
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
tasks: Task[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const TasksDAGView = ({ tasks }: Props) => {
|
||||||
|
const [elements, setElements] = useState<Elements>([]);
|
||||||
|
|
||||||
|
const getNodeType = useCallback(
|
||||||
|
(index: number) => {
|
||||||
|
return index === 0
|
||||||
|
? 'input'
|
||||||
|
: index === tasks.length - 1
|
||||||
|
? 'output'
|
||||||
|
: 'default';
|
||||||
|
},
|
||||||
|
[tasks]
|
||||||
|
);
|
||||||
|
|
||||||
|
const nodes: Node[] = useMemo(() => {
|
||||||
|
const posY = 0;
|
||||||
|
let posX = 0;
|
||||||
|
const deltaX = 250;
|
||||||
|
|
||||||
|
return tasks.map((task, index) => {
|
||||||
|
posX += deltaX;
|
||||||
|
|
||||||
|
return {
|
||||||
|
className: 'leaf-node',
|
||||||
|
id: replaceSpaceWith_(task.name),
|
||||||
|
type: getNodeType(index),
|
||||||
|
data: { label: getEntityName(task as EntityReference) },
|
||||||
|
position: { x: posX, y: posY },
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}, [tasks]);
|
||||||
|
|
||||||
|
const edges: Edge[] = useMemo(() => {
|
||||||
|
return tasks.reduce((prev, task) => {
|
||||||
|
const src = replaceSpaceWith_(task.name);
|
||||||
|
const taskEdges = (task.downstreamTasks || []).map((dwTask) => {
|
||||||
|
const dest = replaceSpaceWith_(dwTask);
|
||||||
|
|
||||||
|
return {
|
||||||
|
arrowHeadType: ArrowHeadType.ArrowClosed,
|
||||||
|
id: `${src}-${dest}`,
|
||||||
|
type: 'straight',
|
||||||
|
source: src,
|
||||||
|
target: dest,
|
||||||
|
label: '',
|
||||||
|
} as Edge;
|
||||||
|
});
|
||||||
|
|
||||||
|
return [...prev, ...taskEdges];
|
||||||
|
}, [] as Edge[]);
|
||||||
|
}, [tasks]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setElements(getLayoutedElements([...nodes, ...edges]));
|
||||||
|
}, [nodes, edges]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ReactFlow
|
||||||
|
data-testid="react-flow-component"
|
||||||
|
elements={elements}
|
||||||
|
maxZoom={2}
|
||||||
|
minZoom={0.5}
|
||||||
|
selectNodesOnDrag={false}
|
||||||
|
zoomOnDoubleClick={false}
|
||||||
|
zoomOnScroll={false}
|
||||||
|
onLoad={onLoad}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TasksDAGView;
|
@ -627,3 +627,7 @@ export const getExploreLinkByFilter = (
|
|||||||
`${filter}=${getOwnerIds(filter, userDetails, nonSecureUserDetails).join()}`
|
`${filter}=${getOwnerIds(filter, userDetails, nonSecureUserDetails).join()}`
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const replaceSpaceWith_ = (text: string) => {
|
||||||
|
return text.replace(/\s/g, '_');
|
||||||
|
};
|
||||||
|
Loading…
x
Reference in New Issue
Block a user