mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-08-30 20:06:19 +00:00
fix(ui): pipelineDetails page update (#8582)
* fix(ui): pipelineDetails page update * fix Lineage and Activity & Task tab UI * complete ui changes for pipelineDetails * fix failing tests add localizations * fix tree view on execution tab * fix wrapping for tree nodes * comment addressed * added missed localization * add test ids for tabs * fix cypress failing for ingestion * fix missing condition
This commit is contained in:
parent
2e158237ea
commit
d4b2621e9d
@ -625,6 +625,7 @@ const DashboardDetails = ({
|
||||
editChartTags,
|
||||
tagList,
|
||||
deleted,
|
||||
isTagLoading,
|
||||
]
|
||||
);
|
||||
|
||||
@ -782,7 +783,7 @@ const DashboardDetails = ({
|
||||
dashboardDetails as CustomPropertyProps['entityDetails']
|
||||
}
|
||||
entityType={EntityType.DASHBOARD}
|
||||
handleExtentionUpdate={onExtensionUpdate}
|
||||
handleExtensionUpdate={onExtensionUpdate}
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
|
@ -128,7 +128,7 @@ const DatasetDetails: React.FC<DatasetDetailsProps> = ({
|
||||
deletePostHandler,
|
||||
paging,
|
||||
fetchFeedHandler,
|
||||
handleExtentionUpdate,
|
||||
handleExtensionUpdate,
|
||||
updateThreadHandler,
|
||||
entityFieldTaskCount,
|
||||
}: DatasetDetailsProps) => {
|
||||
@ -834,7 +834,7 @@ const DatasetDetails: React.FC<DatasetDetailsProps> = ({
|
||||
tableDetails as CustomPropertyProps['entityDetails']
|
||||
}
|
||||
entityType={EntityType.TABLE}
|
||||
handleExtentionUpdate={handleExtentionUpdate}
|
||||
handleExtensionUpdate={handleExtensionUpdate}
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
|
@ -95,6 +95,6 @@ export interface DatasetDetailsProps {
|
||||
feedType?: FeedFilter,
|
||||
threadType?: ThreadType
|
||||
) => void;
|
||||
handleExtentionUpdate: (updatedTable: Table) => Promise<void>;
|
||||
handleExtensionUpdate: (updatedTable: Table) => Promise<void>;
|
||||
updateThreadHandler: ThreadUpdatedFunc;
|
||||
}
|
||||
|
@ -174,7 +174,7 @@ const DatasetDetailsProps = {
|
||||
deletePostHandler: jest.fn(),
|
||||
tagUpdateHandler: jest.fn(),
|
||||
fetchFeedHandler: jest.fn(),
|
||||
handleExtentionUpdate: jest.fn(),
|
||||
handleExtensionUpdate: jest.fn(),
|
||||
updateThreadHandler: jest.fn(),
|
||||
};
|
||||
|
||||
|
@ -10,11 +10,10 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Card, Col, Empty, Row, Space, Typography } from 'antd';
|
||||
import { Card, Col, Empty, Row, Space, Tooltip, Typography } from 'antd';
|
||||
import { isEmpty, uniqueId } from 'lodash';
|
||||
import React, { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Tooltip } from 'react-tippy';
|
||||
import { PipelineStatus } from '../../../generated/entity/data/pipeline';
|
||||
import { getTreeViewData } from '../../../utils/executionUtils';
|
||||
import { getStatusBadgeIcon } from '../../../utils/PipelineDetailsUtils';
|
||||
@ -80,25 +79,25 @@ const TreeViewTab = ({
|
||||
</Col>
|
||||
|
||||
<Col span={19}>
|
||||
<Space>
|
||||
<div className="execution-node-container">
|
||||
{value.map((status) => (
|
||||
<Tooltip
|
||||
html={
|
||||
key={uniqueId()}
|
||||
placement="top"
|
||||
title={
|
||||
<Space direction="vertical">
|
||||
<div>{status.timestamp}</div>
|
||||
<div>{status.executionStatus}</div>
|
||||
</Space>
|
||||
}
|
||||
key={uniqueId()}
|
||||
position="bottom">
|
||||
}>
|
||||
<SVGIcons
|
||||
alt="result"
|
||||
className="tw-w-6"
|
||||
className="tw-w-6 mr-2 mb-2"
|
||||
icon={getStatusBadgeIcon(status.executionStatus)}
|
||||
/>
|
||||
</Tooltip>
|
||||
))}
|
||||
</Space>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
|
@ -12,7 +12,7 @@
|
||||
*/
|
||||
|
||||
import { Popover, Skeleton, Space } from 'antd';
|
||||
import { capitalize } from 'lodash';
|
||||
import { capitalize, isEmpty } from 'lodash';
|
||||
import React, {
|
||||
FunctionComponent,
|
||||
useCallback,
|
||||
@ -23,6 +23,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { getRunHistoryForPipeline } from '../../../axiosAPIs/ingestionPipelineAPI';
|
||||
import {
|
||||
IngestionPipeline,
|
||||
PipelineState,
|
||||
PipelineStatus,
|
||||
} from '../../../generated/entity/services/ingestionPipelines/ingestionPipeline';
|
||||
import {
|
||||
@ -45,7 +46,7 @@ export const IngestionRecentRuns: FunctionComponent<Props> = ({
|
||||
classNames,
|
||||
}: Props) => {
|
||||
const { t } = useTranslation();
|
||||
const [recentRunStatus, setRecentRunStatus] = useState<PipelineStatus[]>();
|
||||
const [recentRunStatus, setRecentRunStatus] = useState<PipelineStatus[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const fetchPipelineStatus = useCallback(async () => {
|
||||
@ -59,8 +60,8 @@ export const IngestionRecentRuns: FunctionComponent<Props> = ({
|
||||
const runs = response.data.splice(0, 5).reverse() ?? [];
|
||||
|
||||
setRecentRunStatus(
|
||||
runs.length === 0
|
||||
? [ingestion.pipelineStatuses as PipelineStatus]
|
||||
runs.length === 0 && ingestion.pipelineStatuses
|
||||
? [ingestion.pipelineStatuses]
|
||||
: runs
|
||||
);
|
||||
} finally {
|
||||
@ -78,8 +79,14 @@ export const IngestionRecentRuns: FunctionComponent<Props> = ({
|
||||
<Space className={classNames} size={2}>
|
||||
{loading ? (
|
||||
<Skeleton.Input size="small" />
|
||||
) : isEmpty(recentRunStatus) ? (
|
||||
<p
|
||||
className={`tw-h-5 tw-w-16 tw-rounded-sm tw-bg-status-${PipelineState.Queued} tw-px-1 tw-text-white tw-text-center`}
|
||||
data-testid="pipeline-status">
|
||||
{capitalize(PipelineState.Queued)}
|
||||
</p>
|
||||
) : (
|
||||
recentRunStatus?.map((r, i) => {
|
||||
recentRunStatus.map((r, i) => {
|
||||
const status =
|
||||
i === recentRunStatus.length - 1 ? (
|
||||
<p
|
||||
|
@ -664,7 +664,7 @@ const MlModelDetail: FC<MlModelDetailProp> = ({
|
||||
mlModelDetail as CustomPropertyProps['entityDetails']
|
||||
}
|
||||
entityType={EntityType.MLMODEL}
|
||||
handleExtentionUpdate={onExtensionUpdate}
|
||||
handleExtensionUpdate={onExtensionUpdate}
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
|
@ -11,39 +11,77 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { compare } from 'fast-json-patch';
|
||||
import { Card, Col, Row, Space, Table, Tabs, Tooltip } from 'antd';
|
||||
import { ColumnsType } from 'antd/lib/table';
|
||||
import { AxiosError } from 'axios';
|
||||
import { compare, Operation } from 'fast-json-patch';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { EntityTags, ExtraInfo } from 'Models';
|
||||
import React, { RefObject, useCallback, useEffect, useState } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import {
|
||||
EntityFieldThreadCount,
|
||||
EntityTags,
|
||||
ExtraInfo,
|
||||
TagOption,
|
||||
} from 'Models';
|
||||
import React, {
|
||||
RefObject,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Link, Redirect, useHistory, useParams } from 'react-router-dom';
|
||||
import AppState from '../../AppState';
|
||||
import {
|
||||
getAllFeeds,
|
||||
postFeedById,
|
||||
postThread,
|
||||
} from '../../axiosAPIs/feedsAPI';
|
||||
import { getLineageByFQN } from '../../axiosAPIs/lineageAPI';
|
||||
import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants';
|
||||
import { getPipelineDetailsPath, ROUTES } from '../../constants/constants';
|
||||
import { EntityField } from '../../constants/feed.constants';
|
||||
import { NO_PERMISSION_FOR_ACTION } from '../../constants/HelperTextUtil';
|
||||
import { observerOptions } from '../../constants/Mydata.constants';
|
||||
import { PIPELINE_DETAILS_TABS } from '../../constants/pipeline.constants';
|
||||
import { EntityType } from '../../enums/entity.enum';
|
||||
import { FeedFilter } from '../../enums/mydata.enum';
|
||||
import { OwnerType } from '../../enums/user.enum';
|
||||
import { CreateThread } from '../../generated/api/feed/createThread';
|
||||
import {
|
||||
Pipeline,
|
||||
PipelineStatus,
|
||||
TagLabel,
|
||||
Task,
|
||||
} from '../../generated/entity/data/pipeline';
|
||||
import { ThreadType } from '../../generated/entity/feed/thread';
|
||||
import { Post, 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 { LabelType, State } from '../../generated/type/tagLabel';
|
||||
import { useInfiniteScroll } from '../../hooks/useInfiniteScroll';
|
||||
import jsonData from '../../jsons/en';
|
||||
import {
|
||||
getCountBadge,
|
||||
getCurrentUserId,
|
||||
getEntityName,
|
||||
getEntityPlaceHolder,
|
||||
getFeedCounts,
|
||||
getOwnerValue,
|
||||
} from '../../utils/CommonUtils';
|
||||
import { getEntityFeedLink } from '../../utils/EntityUtils';
|
||||
import { getDefaultValue } from '../../utils/FeedElementUtils';
|
||||
import { getEntityFieldThreadCounts } from '../../utils/FeedUtils';
|
||||
import {
|
||||
deletePost,
|
||||
getEntityFieldThreadCounts,
|
||||
updateThreadData,
|
||||
} from '../../utils/FeedUtils';
|
||||
import { DEFAULT_ENTITY_PERMISSION } from '../../utils/PermissionsUtils';
|
||||
import { getLineageViewPath } from '../../utils/RouterUtils';
|
||||
import { getTagsWithoutTier } from '../../utils/TableUtils';
|
||||
import SVGIcons from '../../utils/SvgUtils';
|
||||
import { getTagsWithoutTier, getTierTags } from '../../utils/TableUtils';
|
||||
import { fetchTagsAndGlossaryTerms } from '../../utils/TagsUtils';
|
||||
import { getDateTimeByTimeStamp } from '../../utils/TimeUtils';
|
||||
import { showErrorToast } from '../../utils/ToastUtils';
|
||||
import ActivityFeedList from '../ActivityFeed/ActivityFeedList/ActivityFeedList';
|
||||
import ActivityThreadPanel from '../ActivityFeed/ActivityThreadPanel/ActivityThreadPanel';
|
||||
@ -51,7 +89,7 @@ import { CustomPropertyTable } from '../common/CustomPropertyTable/CustomPropert
|
||||
import { CustomPropertyProps } from '../common/CustomPropertyTable/CustomPropertyTable.interface';
|
||||
import Description from '../common/description/Description';
|
||||
import EntityPageInfo from '../common/entityPageInfo/EntityPageInfo';
|
||||
import TabsPane from '../common/TabsPane/TabsPane';
|
||||
import RichTextEditorPreviewer from '../common/rich-text-editor/RichTextEditorPreviewer';
|
||||
import PageContainer from '../containers/PageContainer';
|
||||
import EntityLineageComponent from '../EntityLineage/EntityLineage.component';
|
||||
import ExecutionsTab from '../Execution/Execution.component';
|
||||
@ -60,23 +98,17 @@ import { ModalWithMarkdownEditor } from '../Modals/ModalWithMarkdownEditor/Modal
|
||||
import RequestDescriptionModal from '../Modals/RequestDescriptionModal/RequestDescriptionModal';
|
||||
import { usePermissionProvider } from '../PermissionProvider/PermissionProvider';
|
||||
import { ResourceEntity } from '../PermissionProvider/PermissionProvider.interface';
|
||||
import TagsContainer from '../tags-container/tags-container';
|
||||
import TagsViewer from '../tags-viewer/tags-viewer';
|
||||
import TasksDAGView from '../TasksDAGView/TasksDAGView';
|
||||
import { PipeLineDetailsProp } from './PipelineDetails.interface';
|
||||
|
||||
const PipelineDetails = ({
|
||||
entityName,
|
||||
owner,
|
||||
tier,
|
||||
slashedPipelineName,
|
||||
pipelineTags,
|
||||
activeTab,
|
||||
pipelineUrl,
|
||||
pipelineDetails,
|
||||
serviceType,
|
||||
setActiveTabHandler,
|
||||
description,
|
||||
descriptionUpdateHandler,
|
||||
entityLineage,
|
||||
followers,
|
||||
followPipelineHandler,
|
||||
unfollowPipelineHandler,
|
||||
@ -87,29 +119,43 @@ const PipelineDetails = ({
|
||||
loadNodeHandler,
|
||||
lineageLeafNodes,
|
||||
isNodeLoading,
|
||||
version,
|
||||
deleted,
|
||||
versionHandler,
|
||||
addLineageHandler,
|
||||
removeLineageHandler,
|
||||
entityLineageHandler,
|
||||
isLineageLoading,
|
||||
isentityThreadLoading,
|
||||
entityThread,
|
||||
postFeedHandler,
|
||||
feedCount,
|
||||
entityFieldThreadCount,
|
||||
createThread,
|
||||
pipelineFQN,
|
||||
deletePostHandler,
|
||||
paging,
|
||||
fetchFeedHandler,
|
||||
pipelineStatus,
|
||||
updateThreadHandler,
|
||||
entityFieldTaskCount,
|
||||
onExtensionUpdate,
|
||||
}: PipeLineDetailsProp) => {
|
||||
const history = useHistory();
|
||||
const { tab } = useParams<{ tab: PIPELINE_DETAILS_TABS }>();
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
tier,
|
||||
deleted,
|
||||
owner,
|
||||
serviceType,
|
||||
description,
|
||||
version,
|
||||
pipelineStatus,
|
||||
tags,
|
||||
} = useMemo(() => {
|
||||
return {
|
||||
deleted: pipelineDetails.deleted,
|
||||
owner: pipelineDetails.owner,
|
||||
serviceType: pipelineDetails.serviceType,
|
||||
description: pipelineDetails.description,
|
||||
version: pipelineDetails.version,
|
||||
pipelineStatus: pipelineDetails.pipelineStatus,
|
||||
tier: getTierTags(pipelineDetails.tags ?? []),
|
||||
tags: getTagsWithoutTier(pipelineDetails.tags ?? []),
|
||||
};
|
||||
}, [pipelineDetails]);
|
||||
|
||||
// local state variables
|
||||
const [editTaskTags, setEditTaskTags] = useState<{
|
||||
task: Task;
|
||||
index: number;
|
||||
}>();
|
||||
const [isEdit, setIsEdit] = useState(false);
|
||||
const [followersCount, setFollowersCount] = useState(0);
|
||||
const [isFollowing, setIsFollowing] = useState(false);
|
||||
@ -117,6 +163,25 @@ const PipelineDetails = ({
|
||||
task: Task;
|
||||
index: number;
|
||||
}>();
|
||||
const [lineageLoading, setLineageLoading] = useState(false);
|
||||
const [entityLineage, setEntityLineage] = useState<EntityLineage>(
|
||||
{} as EntityLineage
|
||||
);
|
||||
const [entityThreadLoading, setEntityThreadLoading] = useState(false);
|
||||
const [entityThreads, setEntityThreads] = useState<Thread[]>([]);
|
||||
const [entityThreadPaging, setEntityThreadPaging] = useState<Paging>({
|
||||
total: 0,
|
||||
} as Paging);
|
||||
|
||||
const [feedCount, setFeedCount] = useState<number>(0);
|
||||
const [entityFieldThreadCount, setEntityFieldThreadCount] = useState<
|
||||
EntityFieldThreadCount[]
|
||||
>([]);
|
||||
const [entityFieldTaskCount, setEntityFieldTaskCount] = useState<
|
||||
EntityFieldThreadCount[]
|
||||
>([]);
|
||||
|
||||
const [tagList, setTagList] = useState<TagOption[]>();
|
||||
|
||||
const [threadLink, setThreadLink] = useState<string>('');
|
||||
|
||||
@ -134,8 +199,20 @@ const PipelineDetails = ({
|
||||
DEFAULT_ENTITY_PERMISSION
|
||||
);
|
||||
|
||||
// local state ends
|
||||
|
||||
const USERId = getCurrentUserId();
|
||||
const { getEntityPermission } = usePermissionProvider();
|
||||
|
||||
const tasksInternal = useMemo(
|
||||
() => tasks.map((t) => ({ ...t, tags: t.tags ?? [] })),
|
||||
[tasks]
|
||||
);
|
||||
|
||||
const onEntityFieldSelect = (value: string) => {
|
||||
setSelectedField(value);
|
||||
};
|
||||
|
||||
const fetchResourcePermission = useCallback(async () => {
|
||||
try {
|
||||
const entityPermission = await getEntityPermission(
|
||||
@ -156,9 +233,6 @@ const PipelineDetails = ({
|
||||
}
|
||||
}, [pipelineDetails.id]);
|
||||
|
||||
const onEntityFieldSelect = (value: string) => {
|
||||
setSelectedField(value);
|
||||
};
|
||||
const closeRequestModal = () => {
|
||||
setSelectedField('');
|
||||
};
|
||||
@ -169,63 +243,11 @@ const PipelineDetails = ({
|
||||
);
|
||||
setFollowersCount(followers?.length);
|
||||
};
|
||||
const tabs = [
|
||||
{
|
||||
name: 'Details',
|
||||
icon: {
|
||||
alt: 'schema',
|
||||
name: 'icon-schema',
|
||||
title: 'Details',
|
||||
selectedName: 'icon-schemacolor',
|
||||
},
|
||||
isProtected: false,
|
||||
position: 1,
|
||||
},
|
||||
{
|
||||
name: 'Activity Feeds & Tasks',
|
||||
icon: {
|
||||
alt: 'activity_feed',
|
||||
name: 'activity_feed',
|
||||
title: 'Activity Feed',
|
||||
selectedName: 'activity-feed-color',
|
||||
},
|
||||
isProtected: false,
|
||||
position: 2,
|
||||
count: feedCount,
|
||||
},
|
||||
{
|
||||
name: 'Executions',
|
||||
icon: {
|
||||
alt: 'executions',
|
||||
name: 'executions',
|
||||
title: 'Executions',
|
||||
selectedName: 'activity-feed-color',
|
||||
},
|
||||
isProtected: false,
|
||||
position: 3,
|
||||
},
|
||||
{
|
||||
name: 'Lineage',
|
||||
icon: {
|
||||
alt: 'lineage',
|
||||
name: 'icon-lineage',
|
||||
title: 'Lineage',
|
||||
selectedName: 'icon-lineagecolor',
|
||||
},
|
||||
isProtected: false,
|
||||
position: 4,
|
||||
},
|
||||
{
|
||||
name: 'Custom Properties',
|
||||
isProtected: false,
|
||||
position: 5,
|
||||
},
|
||||
];
|
||||
|
||||
const extraInfo: Array<ExtraInfo> = [
|
||||
{
|
||||
key: 'Owner',
|
||||
value: getOwnerValue(owner),
|
||||
value: owner && getOwnerValue(owner),
|
||||
placeholderText: getEntityPlaceHolder(
|
||||
getEntityName(owner),
|
||||
owner?.deleted
|
||||
@ -378,7 +400,41 @@ const PipelineDetails = ({
|
||||
};
|
||||
|
||||
const getLoader = () => {
|
||||
return isentityThreadLoading ? <Loader /> : null;
|
||||
return entityThreadLoading ? <Loader /> : null;
|
||||
};
|
||||
|
||||
const getFeedData = (
|
||||
after?: string,
|
||||
feedFilter?: FeedFilter,
|
||||
threadType?: ThreadType
|
||||
) => {
|
||||
setEntityThreadLoading(true);
|
||||
getAllFeeds(
|
||||
getEntityFeedLink(EntityType.PIPELINE, pipelineFQN),
|
||||
after,
|
||||
threadType,
|
||||
feedFilter,
|
||||
undefined,
|
||||
USERId
|
||||
)
|
||||
.then((res) => {
|
||||
const { data, paging: pagingObj } = res;
|
||||
if (data) {
|
||||
setEntityThreadPaging(pagingObj);
|
||||
setEntityThreads((prevData) => [...prevData, ...data]);
|
||||
} else {
|
||||
showErrorToast(
|
||||
jsonData['api-error-messages']['fetch-entity-feed-error']
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch((err: AxiosError) => {
|
||||
showErrorToast(
|
||||
err,
|
||||
jsonData['api-error-messages']['fetch-entity-feed-error']
|
||||
);
|
||||
})
|
||||
.finally(() => setEntityThreadLoading(false));
|
||||
};
|
||||
|
||||
const fetchMoreThread = (
|
||||
@ -387,7 +443,7 @@ const PipelineDetails = ({
|
||||
isLoading: boolean
|
||||
) => {
|
||||
if (isElementInView && pagingObj?.after && !isLoading) {
|
||||
fetchFeedHandler(pagingObj.after);
|
||||
getFeedData(pagingObj.after);
|
||||
}
|
||||
};
|
||||
|
||||
@ -396,16 +452,313 @@ const PipelineDetails = ({
|
||||
}, [followers]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchMoreThread(isInView as boolean, paging, isentityThreadLoading);
|
||||
}, [paging, isentityThreadLoading, isInView]);
|
||||
fetchMoreThread(
|
||||
isInView as boolean,
|
||||
entityThreadPaging,
|
||||
entityThreadLoading
|
||||
);
|
||||
}, [entityThreadPaging, entityThreadLoading, isInView]);
|
||||
|
||||
const handleFeedFilterChange = useCallback(
|
||||
(feedFilter, threadType) => {
|
||||
fetchFeedHandler(paging.after, feedFilter, threadType);
|
||||
getFeedData(entityThreadPaging.after, feedFilter, threadType);
|
||||
},
|
||||
[paging]
|
||||
[entityThreadPaging]
|
||||
);
|
||||
|
||||
const handleEditTaskTag = (task: Task, index: number): void => {
|
||||
setEditTaskTags({ task: { ...task, tags: [] }, index });
|
||||
};
|
||||
|
||||
const handleTableTagSelection = (selectedTags?: Array<EntityTags>) => {
|
||||
if (selectedTags && editTask) {
|
||||
const prevTags = editTask.task.tags?.filter((tag) =>
|
||||
selectedTags.some((selectedTag) => selectedTag.tagFQN === tag.tagFQN)
|
||||
);
|
||||
|
||||
const newTags = selectedTags
|
||||
.filter(
|
||||
(selectedTag) =>
|
||||
!editTask.task.tags?.some(
|
||||
(tag) => tag.tagFQN === selectedTag.tagFQN
|
||||
)
|
||||
)
|
||||
.map((tag) => ({
|
||||
labelType: 'Manual',
|
||||
state: 'Confirmed',
|
||||
source: tag.source,
|
||||
tagFQN: tag.tagFQN,
|
||||
}));
|
||||
|
||||
const updatedTasks: Task[] = [...(pipelineDetails.tasks || [])];
|
||||
|
||||
const updatedTask = {
|
||||
...editTask.task,
|
||||
tags: [...(prevTags as TagLabel[]), ...newTags],
|
||||
} as Task;
|
||||
|
||||
updatedTasks[editTask.index] = updatedTask;
|
||||
|
||||
const updatedPipeline = { ...pipelineDetails, tasks: updatedTasks };
|
||||
const jsonPatch = compare(pipelineDetails, updatedPipeline);
|
||||
|
||||
taskUpdateHandler(jsonPatch);
|
||||
}
|
||||
setEditTaskTags(undefined);
|
||||
};
|
||||
|
||||
useMemo(() => {
|
||||
fetchTagsAndGlossaryTerms().then((response) => {
|
||||
setTagList(response);
|
||||
});
|
||||
}, [setTagList]);
|
||||
|
||||
const renderTags = useCallback(
|
||||
(text, record, index) => (
|
||||
<div
|
||||
className="relative tableBody-cell"
|
||||
data-testid="tags-wrapper"
|
||||
onClick={() => handleEditTaskTag(record, index)}>
|
||||
{deleted ? (
|
||||
<div className="tw-flex tw-flex-wrap">
|
||||
<TagsViewer sizeCap={-1} tags={text || []} />
|
||||
</div>
|
||||
) : (
|
||||
<TagsContainer
|
||||
editable={editTaskTags?.index === index}
|
||||
selectedTags={text as EntityTags[]}
|
||||
showAddTagButton={
|
||||
pipelinePermissions.EditAll || pipelinePermissions.EditTags
|
||||
}
|
||||
size="small"
|
||||
tagList={tagList ?? []}
|
||||
type="label"
|
||||
onCancel={() => {
|
||||
handleTableTagSelection();
|
||||
}}
|
||||
onSelectionChange={(tags) => {
|
||||
handleTableTagSelection(tags);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
[
|
||||
tagList,
|
||||
editTaskTags,
|
||||
pipelinePermissions.EditAll,
|
||||
pipelinePermissions.EditTags,
|
||||
deleted,
|
||||
]
|
||||
);
|
||||
|
||||
const taskColumns: ColumnsType<Task> = useMemo(
|
||||
() => [
|
||||
{
|
||||
key: 'name',
|
||||
dataIndex: 'name',
|
||||
title: t('label.name'),
|
||||
render: (name, record) => (
|
||||
<Link target="_blank" to={{ pathname: record.taskUrl }}>
|
||||
<span>{name}</span>
|
||||
<SVGIcons
|
||||
alt="external-link"
|
||||
className="align-middle m-l-xs"
|
||||
icon="external-link"
|
||||
width="16px"
|
||||
/>
|
||||
</Link>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'type',
|
||||
dataIndex: 'taskType',
|
||||
width: 180,
|
||||
title: t('label.type'),
|
||||
},
|
||||
{
|
||||
key: 'startDate',
|
||||
dataIndex: 'startDate',
|
||||
width: 180,
|
||||
title: t('label.start-date'),
|
||||
render: (startDate: string) =>
|
||||
getDateTimeByTimeStamp(new Date(startDate).valueOf()),
|
||||
},
|
||||
{
|
||||
key: 'description',
|
||||
dataIndex: 'description',
|
||||
width: 350,
|
||||
title: t('label.description'),
|
||||
render: (text, record, index) => (
|
||||
<Space
|
||||
className="w-full tw-group cursor-pointer"
|
||||
data-testid="description">
|
||||
<div>
|
||||
{text ? (
|
||||
<RichTextEditorPreviewer markdown={text} />
|
||||
) : (
|
||||
<span className="tw-no-description">No description</span>
|
||||
)}
|
||||
</div>
|
||||
{!deleted && (
|
||||
<Tooltip
|
||||
title={
|
||||
pipelinePermissions.EditAll
|
||||
? 'Edit Description'
|
||||
: NO_PERMISSION_FOR_ACTION
|
||||
}>
|
||||
<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"
|
||||
disabled={!pipelinePermissions.EditAll}
|
||||
onClick={() => setEditTask({ task: record, index })}>
|
||||
<SVGIcons
|
||||
alt="edit"
|
||||
icon="icon-edit"
|
||||
title="Edit"
|
||||
width="16px"
|
||||
/>
|
||||
</button>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
{
|
||||
key: 'tags',
|
||||
dataIndex: 'tags',
|
||||
title: t('label.tags'),
|
||||
width: 350,
|
||||
render: renderTags,
|
||||
},
|
||||
],
|
||||
[pipelinePermissions, editTask, editTaskTags, tagList, deleted]
|
||||
);
|
||||
|
||||
const getLineageData = () => {
|
||||
setLineageLoading(true);
|
||||
getLineageByFQN(pipelineFQN, EntityType.PIPELINE)
|
||||
.then((res) => {
|
||||
if (res) {
|
||||
setEntityLineage(res);
|
||||
} else {
|
||||
throw jsonData['api-error-messages']['unexpected-server-response'];
|
||||
}
|
||||
})
|
||||
.catch((err: AxiosError) => {
|
||||
showErrorToast(
|
||||
err,
|
||||
jsonData['api-error-messages']['fetch-lineage-error']
|
||||
);
|
||||
})
|
||||
.finally(() => {
|
||||
setLineageLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
switch (tab) {
|
||||
case PIPELINE_DETAILS_TABS.EntityLineage:
|
||||
!deleted && isEmpty(entityLineage) && getLineageData();
|
||||
|
||||
break;
|
||||
case PIPELINE_DETAILS_TABS.ActivityFeedsAndTasks:
|
||||
getFeedData();
|
||||
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}, [tab]);
|
||||
|
||||
const handleTabChange = (tabValue: string) => {
|
||||
if (tabValue !== tab) {
|
||||
history.push({
|
||||
pathname: getPipelineDetailsPath(pipelineFQN, tabValue),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const getEntityFeedCount = () => {
|
||||
getFeedCounts(
|
||||
EntityType.PIPELINE,
|
||||
pipelineFQN,
|
||||
setEntityFieldThreadCount,
|
||||
setEntityFieldTaskCount,
|
||||
setFeedCount
|
||||
);
|
||||
};
|
||||
|
||||
const postFeedHandler = (value: string, id: string) => {
|
||||
const currentUser = AppState.userDetails?.name ?? AppState.users[0]?.name;
|
||||
|
||||
const data = {
|
||||
message: value,
|
||||
from: currentUser,
|
||||
} as Post;
|
||||
postFeedById(id, data)
|
||||
.then((res) => {
|
||||
if (res) {
|
||||
const { id, posts } = res;
|
||||
setEntityThreads((pre) => {
|
||||
return pre.map((thread) => {
|
||||
if (thread.id === id) {
|
||||
return { ...res, posts: posts?.slice(-3) };
|
||||
} else {
|
||||
return thread;
|
||||
}
|
||||
});
|
||||
});
|
||||
getEntityFeedCount();
|
||||
} else {
|
||||
throw jsonData['api-error-messages']['unexpected-server-response'];
|
||||
}
|
||||
})
|
||||
.catch((err: AxiosError) => {
|
||||
showErrorToast(err, jsonData['api-error-messages']['add-feed-error']);
|
||||
});
|
||||
};
|
||||
|
||||
const createThread = (data: CreateThread) => {
|
||||
postThread(data)
|
||||
.then((res) => {
|
||||
if (res) {
|
||||
setEntityThreads((pre) => [...pre, res]);
|
||||
getEntityFeedCount();
|
||||
} else {
|
||||
showErrorToast(
|
||||
jsonData['api-error-messages']['unexpected-server-response']
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch((err: AxiosError) => {
|
||||
showErrorToast(
|
||||
err,
|
||||
jsonData['api-error-messages']['create-conversation-error']
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
const deletePostHandler = (
|
||||
threadId: string,
|
||||
postId: string,
|
||||
isThread: boolean
|
||||
) => {
|
||||
deletePost(threadId, postId, isThread, setEntityThreads);
|
||||
};
|
||||
|
||||
const updateThreadHandler = (
|
||||
threadId: string,
|
||||
postId: string,
|
||||
isThread: boolean,
|
||||
data: Operation[]
|
||||
) => {
|
||||
updateThreadData(threadId, postId, isThread, data, setEntityThreads);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getEntityFeedCount();
|
||||
}, [pipelineFQN, description, pipelineDetails, tasks]);
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<div className="tw-px-6 tw-w-full tw-h-full tw-flex tw-flex-col">
|
||||
@ -443,7 +796,7 @@ const PipelineDetails = ({
|
||||
? onTierRemove
|
||||
: undefined
|
||||
}
|
||||
tags={pipelineTags}
|
||||
tags={tags}
|
||||
tagsHandler={onTagUpdate}
|
||||
tier={tier}
|
||||
titleLinks={slashedPipelineName}
|
||||
@ -457,139 +810,187 @@ const PipelineDetails = ({
|
||||
? onTierUpdate
|
||||
: undefined
|
||||
}
|
||||
version={version}
|
||||
version={version + ''}
|
||||
versionHandler={versionHandler}
|
||||
onThreadLinkSelect={onThreadLinkSelect}
|
||||
/>
|
||||
<div className="tw-mt-4 tw-flex tw-flex-col tw-flex-grow tw-w-full">
|
||||
<TabsPane
|
||||
activeTab={activeTab}
|
||||
setActiveTab={setActiveTabHandler}
|
||||
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-bg-white tw-shadow tw-rounded-md tw-w-full">
|
||||
{activeTab === 1 && (
|
||||
<>
|
||||
<div className="tw-grid tw-grid-cols-4 tw-gap-4 tw-w-full">
|
||||
<div className="tw-col-span-full tw--ml-5">
|
||||
<Description
|
||||
description={description}
|
||||
entityFieldTasks={getEntityFieldThreadCounts(
|
||||
EntityField.DESCRIPTION,
|
||||
entityFieldTaskCount
|
||||
)}
|
||||
entityFieldThreads={getEntityFieldThreadCounts(
|
||||
EntityField.DESCRIPTION,
|
||||
entityFieldThreadCount
|
||||
)}
|
||||
entityFqn={pipelineFQN}
|
||||
entityName={entityName}
|
||||
entityType={EntityType.PIPELINE}
|
||||
hasEditAccess={
|
||||
pipelinePermissions.EditAll ||
|
||||
pipelinePermissions.EditDescription
|
||||
}
|
||||
isEdit={isEdit}
|
||||
isReadOnly={deleted}
|
||||
owner={owner}
|
||||
onCancel={onCancel}
|
||||
onDescriptionEdit={onDescriptionEdit}
|
||||
onDescriptionUpdate={onDescriptionUpdate}
|
||||
onEntityFieldSelect={onEntityFieldSelect}
|
||||
onThreadLinkSelect={onThreadLinkSelect}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<hr className="tw-my-3" />
|
||||
<div
|
||||
className="tw-flex-grow tw-w-full tw-h-full"
|
||||
style={{ height: 'calc(100% - 250px)' }}>
|
||||
{!isEmpty(tasks) ? (
|
||||
<Tabs activeKey={tab} className="h-full" onChange={handleTabChange}>
|
||||
<Tabs.TabPane
|
||||
key={PIPELINE_DETAILS_TABS.Tasks}
|
||||
tab={
|
||||
<span data-testid={PIPELINE_DETAILS_TABS.Tasks}>
|
||||
{t('label.tasks')}
|
||||
</span>
|
||||
}>
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={24}>
|
||||
<Description
|
||||
description={description}
|
||||
entityFieldTasks={getEntityFieldThreadCounts(
|
||||
EntityField.DESCRIPTION,
|
||||
entityFieldTaskCount
|
||||
)}
|
||||
entityFieldThreads={getEntityFieldThreadCounts(
|
||||
EntityField.DESCRIPTION,
|
||||
entityFieldThreadCount
|
||||
)}
|
||||
entityFqn={pipelineFQN}
|
||||
entityName={entityName}
|
||||
entityType={EntityType.PIPELINE}
|
||||
hasEditAccess={
|
||||
pipelinePermissions.EditAll ||
|
||||
pipelinePermissions.EditDescription
|
||||
}
|
||||
isEdit={isEdit}
|
||||
isReadOnly={deleted}
|
||||
owner={owner}
|
||||
onCancel={onCancel}
|
||||
onDescriptionEdit={onDescriptionEdit}
|
||||
onDescriptionUpdate={onDescriptionUpdate}
|
||||
onEntityFieldSelect={onEntityFieldSelect}
|
||||
onThreadLinkSelect={onThreadLinkSelect}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Table
|
||||
bordered
|
||||
columns={taskColumns}
|
||||
dataSource={tasksInternal}
|
||||
pagination={false}
|
||||
rowKey="name"
|
||||
size="small"
|
||||
/>
|
||||
</Col>
|
||||
{!isEmpty(tasks) ? (
|
||||
<Col span={24}>
|
||||
<Card title={t('label.dag-view')}>
|
||||
<div className="h-100">
|
||||
<TasksDAGView
|
||||
selectedExec={selectedExecution}
|
||||
tasks={tasks}
|
||||
/>
|
||||
) : (
|
||||
<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"
|
||||
data-testid="no-tasks-data">
|
||||
<span>No task data is available</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
{activeTab === 2 && (
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
) : (
|
||||
<div
|
||||
className="tw-py-4 tw-px-7 tw-grid tw-grid-cols-3 entity-feed-list tw--mx-7 tw--my-4"
|
||||
id="activityfeed">
|
||||
<div />
|
||||
<ActivityFeedList
|
||||
isEntityFeed
|
||||
withSidePanel
|
||||
className=""
|
||||
deletePostHandler={deletePostHandler}
|
||||
entityName={entityName}
|
||||
feedList={entityThread}
|
||||
postFeedHandler={postFeedHandler}
|
||||
updateThreadHandler={updateThreadHandler}
|
||||
onFeedFiltersUpdate={handleFeedFilterChange}
|
||||
/>
|
||||
<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"
|
||||
data-testid="no-tasks-data">
|
||||
<span>{t('label.no-task-available')}</span>
|
||||
</div>
|
||||
)}
|
||||
{activeTab === 3 && <ExecutionsTab pipelineFQN={pipelineFQN} />}
|
||||
{activeTab === 4 && (
|
||||
<div className="h-full">
|
||||
<EntityLineageComponent
|
||||
addLineageHandler={addLineageHandler}
|
||||
deleted={deleted}
|
||||
entityLineage={entityLineage}
|
||||
entityLineageHandler={entityLineageHandler}
|
||||
entityType={EntityType.PIPELINE}
|
||||
hasEditAccess={
|
||||
pipelinePermissions.EditAll ||
|
||||
pipelinePermissions.EditLineage
|
||||
}
|
||||
isLoading={isLineageLoading}
|
||||
isNodeLoading={isNodeLoading}
|
||||
lineageLeafNodes={lineageLeafNodes}
|
||||
loadNodeHandler={loadNodeHandler}
|
||||
removeLineageHandler={removeLineageHandler}
|
||||
onFullScreenClick={handleFullScreenClick}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{activeTab === 5 && (
|
||||
<CustomPropertyTable
|
||||
entityDetails={
|
||||
pipelineDetails as CustomPropertyProps['entityDetails']
|
||||
}
|
||||
entityType={EntityType.PIPELINE}
|
||||
handleExtentionUpdate={onExtensionUpdate}
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
data-testid="observer-element"
|
||||
id="observer-element"
|
||||
ref={elementRef as RefObject<HTMLDivElement>}>
|
||||
{getLoader()}
|
||||
</div>
|
||||
</Row>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane
|
||||
className="h-full"
|
||||
key={PIPELINE_DETAILS_TABS.ActivityFeedsAndTasks}
|
||||
tab={
|
||||
<span data-testid={PIPELINE_DETAILS_TABS.ActivityFeedsAndTasks}>
|
||||
{t('label.activity-feed-and-task-plural')}{' '}
|
||||
{getCountBadge(
|
||||
feedCount,
|
||||
'',
|
||||
PIPELINE_DETAILS_TABS.ActivityFeedsAndTasks === tab
|
||||
)}
|
||||
</span>
|
||||
}>
|
||||
<Card className="h-min-full">
|
||||
<Row justify="center">
|
||||
<Col span={18}>
|
||||
<div id="activityfeed">
|
||||
<ActivityFeedList
|
||||
isEntityFeed
|
||||
withSidePanel
|
||||
deletePostHandler={deletePostHandler}
|
||||
entityName={entityName}
|
||||
feedList={entityThreads}
|
||||
postFeedHandler={postFeedHandler}
|
||||
updateThreadHandler={updateThreadHandler}
|
||||
onFeedFiltersUpdate={handleFeedFilterChange}
|
||||
/>
|
||||
<div
|
||||
data-testid="observer-element"
|
||||
id="observer-element"
|
||||
ref={elementRef as RefObject<HTMLDivElement>}>
|
||||
{getLoader()}
|
||||
</div>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
</Tabs.TabPane>
|
||||
|
||||
<Tabs.TabPane
|
||||
key={PIPELINE_DETAILS_TABS.Executions}
|
||||
tab={
|
||||
<span data-testid={PIPELINE_DETAILS_TABS.Tasks}>
|
||||
{t('label.executions')}
|
||||
</span>
|
||||
}>
|
||||
<ExecutionsTab pipelineFQN={pipelineFQN} />
|
||||
</Tabs.TabPane>
|
||||
|
||||
<Tabs.TabPane
|
||||
key={PIPELINE_DETAILS_TABS.EntityLineage}
|
||||
tab={
|
||||
<span data-testid="Lineage">{t('label.entity-lineage')}</span>
|
||||
}>
|
||||
<div className="h-full bg-white">
|
||||
<EntityLineageComponent
|
||||
addLineageHandler={addLineageHandler}
|
||||
deleted={deleted}
|
||||
entityLineage={entityLineage}
|
||||
entityLineageHandler={entityLineageHandler}
|
||||
entityType={EntityType.PIPELINE}
|
||||
hasEditAccess={
|
||||
pipelinePermissions.EditAll || pipelinePermissions.EditLineage
|
||||
}
|
||||
isLoading={lineageLoading}
|
||||
isNodeLoading={isNodeLoading}
|
||||
lineageLeafNodes={lineageLeafNodes}
|
||||
loadNodeHandler={loadNodeHandler}
|
||||
removeLineageHandler={removeLineageHandler}
|
||||
onFullScreenClick={handleFullScreenClick}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Tabs.TabPane>
|
||||
|
||||
<Tabs.TabPane
|
||||
key={PIPELINE_DETAILS_TABS.CustomProperties}
|
||||
tab={
|
||||
<span data-testid="Custom Properties">
|
||||
{t('label.custom-properties')}
|
||||
</span>
|
||||
}>
|
||||
<CustomPropertyTable
|
||||
entityDetails={
|
||||
pipelineDetails as CustomPropertyProps['entityDetails']
|
||||
}
|
||||
entityType={EntityType.PIPELINE}
|
||||
handleExtensionUpdate={onExtensionUpdate}
|
||||
/>
|
||||
</Tabs.TabPane>
|
||||
<Tabs.TabPane key="*" tab="">
|
||||
<Redirect to={ROUTES.NOT_FOUND} />
|
||||
</Tabs.TabPane>
|
||||
</Tabs>
|
||||
</div>
|
||||
|
||||
{editTask && (
|
||||
<ModalWithMarkdownEditor
|
||||
header={`Edit Task: "${editTask.task.displayName}"`}
|
||||
placeholder="Enter Task Description"
|
||||
header={`${t('label.edit-task')}: "${
|
||||
editTask.task.displayName || editTask.task.name
|
||||
}"`}
|
||||
placeholder={t('label.type-field-name', {
|
||||
fieldName: t('label.description'),
|
||||
})}
|
||||
value={editTask.task.description || ''}
|
||||
onCancel={closeEditTaskModal}
|
||||
onSave={onTaskUpdate}
|
||||
/>
|
||||
)}
|
||||
|
||||
{threadLink ? (
|
||||
<ActivityThreadPanel
|
||||
createThread={createThread}
|
||||
@ -606,7 +1007,7 @@ const PipelineDetails = ({
|
||||
<RequestDescriptionModal
|
||||
createThread={createThread}
|
||||
defaultValue={getDefaultValue(owner as EntityReference)}
|
||||
header="Request description"
|
||||
header={t('label.request-description')}
|
||||
threadLink={getEntityFeedLink(
|
||||
EntityType.PIPELINE,
|
||||
pipelineFQN,
|
||||
|
@ -12,59 +12,26 @@
|
||||
*/
|
||||
|
||||
import { Operation } from 'fast-json-patch';
|
||||
import {
|
||||
EntityFieldThreadCount,
|
||||
EntityTags,
|
||||
LeafNodes,
|
||||
LineagePos,
|
||||
LoadingNodeState,
|
||||
} from 'Models';
|
||||
import { FeedFilter } from '../../enums/mydata.enum';
|
||||
import { CreateThread } from '../../generated/api/feed/createThread';
|
||||
import { LeafNodes, LineagePos, LoadingNodeState } from 'Models';
|
||||
import { Pipeline, Task } from '../../generated/entity/data/pipeline';
|
||||
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 PipeLineDetailsProp {
|
||||
pipelineFQN: string;
|
||||
version: string;
|
||||
isNodeLoading: LoadingNodeState;
|
||||
lineageLeafNodes: LeafNodes;
|
||||
serviceType: string;
|
||||
pipelineUrl: string;
|
||||
entityName: string;
|
||||
pipelineDetails: Pipeline;
|
||||
activeTab: number;
|
||||
owner: EntityReference;
|
||||
description: string;
|
||||
tier: TagLabel;
|
||||
followers: Array<EntityReference>;
|
||||
pipelineTags: Array<EntityTags>;
|
||||
slashedPipelineName: TitleBreadcrumbProps['titleLinks'];
|
||||
entityLineage: EntityLineage;
|
||||
tasks: Task[];
|
||||
deleted?: boolean;
|
||||
isLineageLoading?: boolean;
|
||||
entityThread: Thread[];
|
||||
isentityThreadLoading: boolean;
|
||||
feedCount: number;
|
||||
entityFieldThreadCount: EntityFieldThreadCount[];
|
||||
entityFieldTaskCount: EntityFieldThreadCount[];
|
||||
paging: Paging;
|
||||
pipelineStatus: Pipeline['pipelineStatus'];
|
||||
fetchFeedHandler: (
|
||||
after?: string,
|
||||
feedFilter?: FeedFilter,
|
||||
threadType?: ThreadType
|
||||
) => void;
|
||||
createThread: (data: CreateThread) => void;
|
||||
setActiveTabHandler: (value: number) => void;
|
||||
followPipelineHandler: () => void;
|
||||
unfollowPipelineHandler: () => void;
|
||||
settingsUpdateHandler: (updatedPipeline: Pipeline) => Promise<void>;
|
||||
@ -76,12 +43,5 @@ export interface PipeLineDetailsProp {
|
||||
addLineageHandler: (edge: Edge) => Promise<void>;
|
||||
removeLineageHandler: (data: EdgeData) => void;
|
||||
entityLineageHandler: (lineage: EntityLineage) => void;
|
||||
postFeedHandler: (value: string, id: string) => void;
|
||||
deletePostHandler: (
|
||||
threadId: string,
|
||||
postId: string,
|
||||
isThread: boolean
|
||||
) => void;
|
||||
updateThreadHandler: ThreadUpdatedFunc;
|
||||
onExtensionUpdate: (updatedPipeline: Pipeline) => Promise<void>;
|
||||
}
|
||||
|
@ -11,10 +11,16 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { findByTestId, findByText, render } from '@testing-library/react';
|
||||
import {
|
||||
findByTestId,
|
||||
findByText,
|
||||
fireEvent,
|
||||
render,
|
||||
} from '@testing-library/react';
|
||||
import { LeafNodes, LoadingNodeState } from 'Models';
|
||||
import React from 'react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { act } from 'react-test-renderer';
|
||||
import { Pipeline } from '../../generated/entity/data/pipeline';
|
||||
import { EntityLineage } from '../../generated/type/entityLineage';
|
||||
import { EntityReference } from '../../generated/type/entityReference';
|
||||
@ -189,7 +195,6 @@ jest.mock('../common/CustomPropertyTable/CustomPropertyTable', () => ({
|
||||
|
||||
jest.mock('../../utils/CommonUtils', () => ({
|
||||
addToRecentViewed: jest.fn(),
|
||||
getCountBadge: jest.fn(),
|
||||
getCurrentUserId: jest.fn().mockReturnValue('CurrentUserId'),
|
||||
getPartialNameFromFQN: jest.fn().mockReturnValue('PartialNameFromFQN'),
|
||||
getUserTeams: () => mockUserTeam,
|
||||
@ -197,11 +202,13 @@ jest.mock('../../utils/CommonUtils', () => ({
|
||||
getEntityPlaceHolder: jest.fn().mockReturnValue('value'),
|
||||
getEntityName: jest.fn().mockReturnValue('entityName'),
|
||||
getOwnerValue: jest.fn().mockReturnValue('Owner'),
|
||||
getFeedCounts: jest.fn(),
|
||||
getCountBadge: jest.fn().mockImplementation((count) => <p>{count}</p>),
|
||||
}));
|
||||
|
||||
jest.mock('', () => ({
|
||||
ExecutionsTab: jest.fn().mockImplementation(() => <p>Executions</p>),
|
||||
}));
|
||||
jest.mock('../Execution/Execution.component', () => {
|
||||
return jest.fn().mockImplementation(() => <p>Executions</p>);
|
||||
});
|
||||
|
||||
describe('Test PipelineDetails component', () => {
|
||||
it('Checks if the PipelineDetails component has all the proper components rendered', async () => {
|
||||
@ -213,20 +220,28 @@ describe('Test PipelineDetails component', () => {
|
||||
);
|
||||
const EntityPageInfo = await findByText(container, /EntityPageInfo/i);
|
||||
const description = await findByText(container, /Description Component/i);
|
||||
const tabs = await findByTestId(container, 'tabs');
|
||||
const detailsTab = await findByTestId(tabs, 'Details');
|
||||
const activityFeedTab = await findByTestId(tabs, 'Activity Feeds & Tasks');
|
||||
const lineageTab = await findByTestId(tabs, 'Lineage');
|
||||
const tasksTab = await findByText(container, 'label.tasks');
|
||||
const activityFeedTab = await findByText(
|
||||
container,
|
||||
'label.activity-feed-and-task-plural'
|
||||
);
|
||||
const lineageTab = await findByText(container, 'label.entity-lineage');
|
||||
const executionsTab = await findByText(container, 'label.executions');
|
||||
const customPropertiesTab = await findByText(
|
||||
container,
|
||||
'label.custom-properties'
|
||||
);
|
||||
|
||||
expect(EntityPageInfo).toBeInTheDocument();
|
||||
expect(description).toBeInTheDocument();
|
||||
expect(tabs).toBeInTheDocument();
|
||||
expect(detailsTab).toBeInTheDocument();
|
||||
expect(tasksTab).toBeInTheDocument();
|
||||
expect(activityFeedTab).toBeInTheDocument();
|
||||
expect(lineageTab).toBeInTheDocument();
|
||||
expect(executionsTab).toBeInTheDocument();
|
||||
expect(customPropertiesTab).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Check if active tab is details', async () => {
|
||||
it('Check if active tab is tasks', async () => {
|
||||
const { container } = render(
|
||||
<PipelineDetails {...PipelineDetailsProps} />,
|
||||
{
|
||||
@ -251,23 +266,39 @@ describe('Test PipelineDetails component', () => {
|
||||
|
||||
it('Check if active tab is activity feed', async () => {
|
||||
const { container } = render(
|
||||
<PipelineDetails {...PipelineDetailsProps} activeTab={2} />,
|
||||
<PipelineDetails {...PipelineDetailsProps} />,
|
||||
{
|
||||
wrapper: MemoryRouter,
|
||||
}
|
||||
);
|
||||
|
||||
const activityFeedTab = await findByText(
|
||||
container,
|
||||
'label.activity-feed-and-task-plural'
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(activityFeedTab);
|
||||
});
|
||||
|
||||
const activityFeedList = await findByText(container, /ActivityFeedList/i);
|
||||
|
||||
expect(activityFeedList).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render execution tab if active tab is 3', async () => {
|
||||
it('should render execution tab', async () => {
|
||||
const { container } = render(
|
||||
<PipelineDetails {...PipelineDetailsProps} activeTab={3} />,
|
||||
<PipelineDetails {...PipelineDetailsProps} />,
|
||||
{
|
||||
wrapper: MemoryRouter,
|
||||
}
|
||||
);
|
||||
|
||||
const activityFeedTab = await findByText(container, 'label.executions');
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(activityFeedTab);
|
||||
});
|
||||
const executions = await findByText(container, 'Executions');
|
||||
|
||||
expect(executions).toBeInTheDocument();
|
||||
@ -275,11 +306,16 @@ describe('Test PipelineDetails component', () => {
|
||||
|
||||
it('Check if active tab is lineage', async () => {
|
||||
const { container } = render(
|
||||
<PipelineDetails {...PipelineDetailsProps} activeTab={4} />,
|
||||
<PipelineDetails {...PipelineDetailsProps} />,
|
||||
{
|
||||
wrapper: MemoryRouter,
|
||||
}
|
||||
);
|
||||
const activityFeedTab = await findByText(container, 'label.entity-lineage');
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(activityFeedTab);
|
||||
});
|
||||
const lineage = await findByTestId(container, 'lineage');
|
||||
|
||||
expect(lineage).toBeInTheDocument();
|
||||
@ -287,11 +323,20 @@ describe('Test PipelineDetails component', () => {
|
||||
|
||||
it('Check if active tab is custom properties', async () => {
|
||||
const { container } = render(
|
||||
<PipelineDetails {...PipelineDetailsProps} activeTab={5} />,
|
||||
<PipelineDetails {...PipelineDetailsProps} />,
|
||||
{
|
||||
wrapper: MemoryRouter,
|
||||
}
|
||||
);
|
||||
|
||||
const activityFeedTab = await findByText(
|
||||
container,
|
||||
'label.custom-properties'
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(activityFeedTab);
|
||||
});
|
||||
const customProperties = await findByText(
|
||||
container,
|
||||
'CustomPropertyTable.component'
|
||||
@ -302,16 +347,23 @@ describe('Test PipelineDetails component', () => {
|
||||
|
||||
it('Should create an observer if IntersectionObserver is available', async () => {
|
||||
const { container } = render(
|
||||
<PipelineDetails {...PipelineDetailsProps} activeTab={5} />,
|
||||
<PipelineDetails {...PipelineDetailsProps} />,
|
||||
{
|
||||
wrapper: MemoryRouter,
|
||||
}
|
||||
);
|
||||
|
||||
const activityFeedTab = await findByText(
|
||||
container,
|
||||
'label.activity-feed-and-task-plural'
|
||||
);
|
||||
|
||||
await act(async () => {
|
||||
fireEvent.click(activityFeedTab);
|
||||
});
|
||||
|
||||
const obServerElement = await findByTestId(container, 'observer-element');
|
||||
|
||||
expect(obServerElement).toBeInTheDocument();
|
||||
|
||||
expect(mockObserve).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
@ -609,7 +609,7 @@ const TopicDetails: React.FC<TopicDetailsProps> = ({
|
||||
topicDetails as CustomPropertyProps['entityDetails']
|
||||
}
|
||||
entityType={EntityType.TOPIC}
|
||||
handleExtentionUpdate={onExtensionUpdate}
|
||||
handleExtensionUpdate={onExtensionUpdate}
|
||||
/>
|
||||
)}
|
||||
<div
|
||||
|
@ -23,5 +23,5 @@ export type EntityDetails = Table & Topic & Dashboard & Pipeline & Mlmodel;
|
||||
export interface CustomPropertyProps {
|
||||
entityDetails: EntityDetails;
|
||||
entityType: EntityType;
|
||||
handleExtentionUpdate: (updatedTable: EntityDetails) => Promise<void>;
|
||||
handleExtensionUpdate: (updatedTable: EntityDetails) => Promise<void>;
|
||||
}
|
||||
|
@ -63,11 +63,11 @@ jest.mock('../../../axiosAPIs/metadataTypeAPI', () => ({
|
||||
}));
|
||||
|
||||
const mockTableDetails = {} as Table & Topic & Dashboard & Pipeline & Mlmodel;
|
||||
const handleExtentionUpdate = jest.fn();
|
||||
const handleExtensionUpdate = jest.fn();
|
||||
|
||||
const mockProp = {
|
||||
entityDetails: mockTableDetails,
|
||||
handleExtentionUpdate,
|
||||
handleExtensionUpdate,
|
||||
entityType: EntityType.TABLE,
|
||||
};
|
||||
|
||||
|
@ -25,7 +25,7 @@ import { PropertyValue } from './PropertyValue';
|
||||
|
||||
export const CustomPropertyTable: FC<CustomPropertyProps> = ({
|
||||
entityDetails,
|
||||
handleExtentionUpdate,
|
||||
handleExtensionUpdate,
|
||||
entityType,
|
||||
}) => {
|
||||
const [entityTypeDetail, setEntityTypeDetail] = useState<Type>({} as Type);
|
||||
@ -41,7 +41,7 @@ export const CustomPropertyTable: FC<CustomPropertyProps> = ({
|
||||
const onExtensionUpdate = async (
|
||||
updatedExtension: CustomPropertyProps['entityDetails']['extension']
|
||||
) => {
|
||||
await handleExtentionUpdate({
|
||||
await handleExtensionUpdate({
|
||||
...entityDetails,
|
||||
extension: updatedExtension,
|
||||
});
|
||||
|
@ -15,7 +15,13 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import classNames from 'classnames';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { EntityTags, TagOption } from 'Models';
|
||||
import React, { Fragment, FunctionComponent, useEffect, useState } from 'react';
|
||||
import React, {
|
||||
Fragment,
|
||||
FunctionComponent,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useState,
|
||||
} from 'react';
|
||||
import AsyncSelect from 'react-select/async';
|
||||
import { FQN_SEPARATOR_CHAR } from '../../constants/char.constants';
|
||||
import { TagSource } from '../../generated/type/tagLabel';
|
||||
@ -87,11 +93,14 @@ const TagsContainer: FunctionComponent<TagsContainerProps> = ({
|
||||
setTags(updatedTags);
|
||||
};
|
||||
|
||||
const handleSave = (event: React.MouseEvent<HTMLElement, MouseEvent>) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
onSelectionChange(tags);
|
||||
};
|
||||
const handleSave = useCallback(
|
||||
(event: React.MouseEvent<HTMLElement, MouseEvent>) => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
onSelectionChange(tags);
|
||||
},
|
||||
[tags]
|
||||
);
|
||||
|
||||
const handleCancel = (event: React.MouseEvent<HTMLElement, MouseEvent>) => {
|
||||
event.preventDefault();
|
||||
|
@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export enum PIPELINE_DETAILS_TABS {
|
||||
Tasks = 'tasks',
|
||||
ActivityFeedsAndTasks = 'activity-feeds-tasks',
|
||||
Executions = 'executions',
|
||||
EntityLineage = 'entity-lineage',
|
||||
CustomProperties = 'custom-properties',
|
||||
}
|
@ -212,6 +212,8 @@
|
||||
"select-rule-effect": "Select Rule Effect",
|
||||
"field-required": "{{field}} is required",
|
||||
"field-required-plural": "{{field}} are required",
|
||||
"edit-task": "Edit Task",
|
||||
"type-filed-name": "Type {{fieldName}}",
|
||||
"no-execution-runs-found": "No execution runs found for the pipeline.",
|
||||
"last-no-of-days": "Last {{day}} Days",
|
||||
"tier-number": "Tier{{tier}}",
|
||||
@ -224,6 +226,12 @@
|
||||
"pipeline": "Pipeline",
|
||||
"function": "Function",
|
||||
"edge-information": "Edge Information",
|
||||
"tasks": "Tasks",
|
||||
"activity-feed-and-task-plural": "Activity Feeds & tasks",
|
||||
"executions": "Executions",
|
||||
"entity-lineage": "Entity Lineage",
|
||||
"custom-properties": "Custom Properties",
|
||||
"dag-view": "DAG view",
|
||||
"read-more": "read more",
|
||||
"read-less": "read less",
|
||||
"no-owner": "No Owner",
|
||||
@ -269,6 +277,7 @@
|
||||
"server": {
|
||||
"no-followed-entities": "You have not followed anything yet.",
|
||||
"no-owned-entities": "You have not owned anything yet.",
|
||||
"no-task-available": "No task data is available",
|
||||
"entity-fetch-error": "Error while fetching {{entity}}",
|
||||
"feed-post-error": "Error while posting the message!",
|
||||
"unexpected-error": "An unexpected error occurred.",
|
||||
|
@ -852,7 +852,7 @@ const DatasetDetailsPage: FunctionComponent = () => {
|
||||
fetchFeedHandler={handleFeedFetchFromFeedList}
|
||||
followTableHandler={followTable}
|
||||
followers={followers}
|
||||
handleExtentionUpdate={handleExtentionUpdate}
|
||||
handleExtensionUpdate={handleExtentionUpdate}
|
||||
isLineageLoading={isLineageLoading}
|
||||
isNodeLoading={isNodeLoading}
|
||||
isQueriesLoading={isTableQueriesLoading}
|
||||
|
@ -42,6 +42,7 @@ import {
|
||||
getTableTabPath,
|
||||
getTopicDetailsPath,
|
||||
} from '../../constants/constants';
|
||||
import { PIPELINE_DETAILS_TABS } from '../../constants/pipeline.constants';
|
||||
import { EntityType, FqnPart } from '../../enums/entity.enum';
|
||||
import { ServiceCategory } from '../../enums/service.enum';
|
||||
import { Dashboard } from '../../generated/entity/data/dashboard';
|
||||
@ -202,7 +203,10 @@ const LineagePage = () => {
|
||||
const pipelineRes = await getPipelineByFqn(entityFQN, '');
|
||||
updateBreadcrumb(
|
||||
pipelineRes,
|
||||
getPipelineDetailsPath(entityFQN, 'lineage')
|
||||
getPipelineDetailsPath(
|
||||
entityFQN,
|
||||
PIPELINE_DETAILS_TABS.EntityLineage
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -13,23 +13,11 @@
|
||||
|
||||
import { AxiosError } from 'axios';
|
||||
import { compare, Operation } from 'fast-json-patch';
|
||||
import { isEmpty, isUndefined, omitBy } from 'lodash';
|
||||
import { isUndefined, omitBy } from 'lodash';
|
||||
import { observer } from 'mobx-react';
|
||||
import {
|
||||
EntityFieldThreadCount,
|
||||
EntityTags,
|
||||
LeafNodes,
|
||||
LineagePos,
|
||||
LoadingNodeState,
|
||||
} from 'Models';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { LeafNodes, LineagePos, LoadingNodeState } from 'Models';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { useHistory, useParams } from 'react-router-dom';
|
||||
import AppState from '../../AppState';
|
||||
import {
|
||||
getAllFeeds,
|
||||
postFeedById,
|
||||
postThread,
|
||||
} from '../../axiosAPIs/feedsAPI';
|
||||
import { getLineageByFQN } from '../../axiosAPIs/lineageAPI';
|
||||
import { addLineage, deleteLineageEdge } from '../../axiosAPIs/miscAPI';
|
||||
import {
|
||||
@ -50,65 +38,45 @@ import { usePermissionProvider } from '../../components/PermissionProvider/Permi
|
||||
import { ResourceEntity } from '../../components/PermissionProvider/PermissionProvider.interface';
|
||||
import PipelineDetails from '../../components/PipelineDetails/PipelineDetails.component';
|
||||
import {
|
||||
getPipelineDetailsPath,
|
||||
getServiceDetailsPath,
|
||||
getVersionPath,
|
||||
} from '../../constants/constants';
|
||||
import { NO_PERMISSION_TO_VIEW } from '../../constants/HelperTextUtil';
|
||||
import { EntityType, TabSpecificField } from '../../enums/entity.enum';
|
||||
import { FeedFilter } from '../../enums/mydata.enum';
|
||||
import { EntityType } from '../../enums/entity.enum';
|
||||
import { ServiceCategory } from '../../enums/service.enum';
|
||||
import { CreateThread } from '../../generated/api/feed/createThread';
|
||||
import { Pipeline, Task } from '../../generated/entity/data/pipeline';
|
||||
import { Post, Thread, ThreadType } from '../../generated/entity/feed/thread';
|
||||
import { Connection } from '../../generated/entity/services/dashboardService';
|
||||
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 jsonData from '../../jsons/en';
|
||||
import {
|
||||
addToRecentViewed,
|
||||
getCurrentUserId,
|
||||
getEntityMissingError,
|
||||
getEntityName,
|
||||
getFeedCounts,
|
||||
} from '../../utils/CommonUtils';
|
||||
import { getEntityFeedLink, getEntityLineage } from '../../utils/EntityUtils';
|
||||
import { deletePost, updateThreadData } from '../../utils/FeedUtils';
|
||||
import { getEntityLineage } from '../../utils/EntityUtils';
|
||||
import { DEFAULT_ENTITY_PERMISSION } from '../../utils/PermissionsUtils';
|
||||
import {
|
||||
defaultFields,
|
||||
getCurrentPipelineTab,
|
||||
pipelineDetailsTabs,
|
||||
} from '../../utils/PipelineDetailsUtils';
|
||||
import { defaultFields } from '../../utils/PipelineDetailsUtils';
|
||||
import { serviceTypeLogo } from '../../utils/ServiceUtils';
|
||||
import { getTagsWithoutTier, getTierTags } from '../../utils/TableUtils';
|
||||
import { showErrorToast } from '../../utils/ToastUtils';
|
||||
|
||||
const PipelineDetailsPage = () => {
|
||||
const USERId = getCurrentUserId();
|
||||
const history = useHistory();
|
||||
|
||||
const { pipelineFQN, tab } = useParams() as Record<string, string>;
|
||||
const { pipelineFQN } = useParams<{ pipelineFQN: string }>();
|
||||
const [pipelineDetails, setPipelineDetails] = useState<Pipeline>(
|
||||
{} as Pipeline
|
||||
);
|
||||
const [pipelineId, setPipelineId] = useState<string>('');
|
||||
|
||||
const [isLoading, setLoading] = useState<boolean>(true);
|
||||
const [isLineageLoading, setIsLineageLoading] = useState<boolean>(false);
|
||||
const [description, setDescription] = useState<string>('');
|
||||
const [followers, setFollowers] = useState<Array<EntityReference>>([]);
|
||||
const [owner, setOwner] = useState<EntityReference>();
|
||||
const [tier, setTier] = useState<TagLabel>();
|
||||
const [tags, setTags] = useState<Array<EntityTags>>([]);
|
||||
const [activeTab, setActiveTab] = useState<number>(
|
||||
getCurrentPipelineTab(tab)
|
||||
);
|
||||
|
||||
const [tasks, setTasks] = useState<Task[]>([]);
|
||||
const [pipelineUrl, setPipelineUrl] = useState<string>('');
|
||||
const [displayName, setDisplayName] = useState<string>('');
|
||||
const [serviceType, setServiceType] = useState<string>('');
|
||||
const [slashedPipelineName, setSlashedPipelineName] = useState<
|
||||
TitleBreadcrumbProps['titleLinks']
|
||||
>([]);
|
||||
@ -122,24 +90,9 @@ const PipelineDetailsPage = () => {
|
||||
);
|
||||
const [leafNodes, setLeafNodes] = useState<LeafNodes>({} as LeafNodes);
|
||||
|
||||
const [currentVersion, setCurrentVersion] = useState<string>();
|
||||
const [deleted, setDeleted] = useState<boolean>(false);
|
||||
const [isError, setIsError] = useState(false);
|
||||
|
||||
const [entityThread, setEntityThread] = useState<Thread[]>([]);
|
||||
const [isentityThreadLoading, setIsentityThreadLoading] =
|
||||
useState<boolean>(false);
|
||||
const [feedCount, setFeedCount] = useState<number>(0);
|
||||
const [entityFieldThreadCount, setEntityFieldThreadCount] = useState<
|
||||
EntityFieldThreadCount[]
|
||||
>([]);
|
||||
const [paging, setPaging] = useState<Paging>({} as Paging);
|
||||
|
||||
const [pipeLineStatus, setPipelineStatus] =
|
||||
useState<Pipeline['pipelineStatus']>();
|
||||
const [entityFieldTaskCount, setEntityFieldTaskCount] = useState<
|
||||
EntityFieldThreadCount[]
|
||||
>([]);
|
||||
const [paging] = useState<Paging>({} as Paging);
|
||||
|
||||
const [pipelinePermissions, setPipelinePermissions] = useState(
|
||||
DEFAULT_ENTITY_PERMISSION
|
||||
@ -164,30 +117,12 @@ const PipelineDetailsPage = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const activeTabHandler = (tabValue: number) => {
|
||||
const currentTabIndex = tabValue - 1;
|
||||
if (pipelineDetailsTabs[currentTabIndex].path !== tab) {
|
||||
setActiveTab(
|
||||
getCurrentPipelineTab(pipelineDetailsTabs[currentTabIndex].path)
|
||||
);
|
||||
history.push({
|
||||
pathname: getPipelineDetailsPath(
|
||||
pipelineFQN,
|
||||
pipelineDetailsTabs[currentTabIndex].path
|
||||
),
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const getEntityFeedCount = () => {
|
||||
getFeedCounts(
|
||||
EntityType.PIPELINE,
|
||||
pipelineFQN,
|
||||
setEntityFieldThreadCount,
|
||||
setEntityFieldTaskCount,
|
||||
setFeedCount
|
||||
);
|
||||
};
|
||||
const { pipelineId, currentVersion } = useMemo(() => {
|
||||
return {
|
||||
pipelineId: pipelineDetails.id,
|
||||
currentVersion: pipelineDetails.version + '',
|
||||
};
|
||||
}, [pipelineDetails]);
|
||||
|
||||
const saveUpdatedPipelineData = (updatedData: Pipeline) => {
|
||||
const jsonPatch = compare(
|
||||
@ -198,70 +133,6 @@ const PipelineDetailsPage = () => {
|
||||
return patchPipelineDetails(pipelineId, jsonPatch);
|
||||
};
|
||||
|
||||
const getLineageData = () => {
|
||||
setIsLineageLoading(true);
|
||||
getLineageByFQN(pipelineFQN, EntityType.PIPELINE)
|
||||
.then((res) => {
|
||||
if (res) {
|
||||
setEntityLineage(res);
|
||||
} else {
|
||||
throw jsonData['api-error-messages']['unexpected-server-response'];
|
||||
}
|
||||
})
|
||||
.catch((err: AxiosError) => {
|
||||
showErrorToast(
|
||||
err,
|
||||
jsonData['api-error-messages']['fetch-lineage-error']
|
||||
);
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLineageLoading(false);
|
||||
});
|
||||
};
|
||||
|
||||
const getFeedData = (
|
||||
after?: string,
|
||||
feedFilter?: FeedFilter,
|
||||
threadType?: ThreadType
|
||||
) => {
|
||||
setIsentityThreadLoading(true);
|
||||
getAllFeeds(
|
||||
getEntityFeedLink(EntityType.PIPELINE, pipelineFQN),
|
||||
after,
|
||||
threadType,
|
||||
feedFilter,
|
||||
undefined,
|
||||
USERId
|
||||
)
|
||||
.then((res) => {
|
||||
const { data, paging: pagingObj } = res;
|
||||
if (data) {
|
||||
setPaging(pagingObj);
|
||||
setEntityThread((prevData) => [...prevData, ...data]);
|
||||
} else {
|
||||
showErrorToast(
|
||||
jsonData['api-error-messages']['fetch-entity-feed-error']
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch((err: AxiosError) => {
|
||||
showErrorToast(
|
||||
err,
|
||||
jsonData['api-error-messages']['fetch-entity-feed-error']
|
||||
);
|
||||
})
|
||||
.finally(() => setIsentityThreadLoading(false));
|
||||
};
|
||||
|
||||
const handleFeedFetchFromFeedList = (
|
||||
after?: string,
|
||||
filterType?: FeedFilter,
|
||||
type?: ThreadType
|
||||
) => {
|
||||
!after && setEntityThread([]);
|
||||
getFeedData(after, filterType, type);
|
||||
};
|
||||
|
||||
const fetchServiceDetails = (type: string, fqn: string) => {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
getServiceByFQN(type + 's', fqn, ['owner'])
|
||||
@ -291,32 +162,16 @@ const PipelineDetailsPage = () => {
|
||||
if (res) {
|
||||
const {
|
||||
id,
|
||||
deleted,
|
||||
description,
|
||||
followers = [],
|
||||
fullyQualifiedName,
|
||||
service,
|
||||
serviceType,
|
||||
tags = [],
|
||||
owner,
|
||||
displayName,
|
||||
name,
|
||||
tasks,
|
||||
pipelineUrl = '',
|
||||
pipelineStatus,
|
||||
version,
|
||||
} = res;
|
||||
setDisplayName(displayName || name);
|
||||
setPipelineDetails(res);
|
||||
setCurrentVersion(version + '');
|
||||
setPipelineId(id);
|
||||
setDescription(description ?? '');
|
||||
setFollowers(followers);
|
||||
setOwner(owner);
|
||||
setTier(getTierTags(tags));
|
||||
setTags(getTagsWithoutTier(tags));
|
||||
setServiceType(serviceType ?? '');
|
||||
setDeleted(Boolean(deleted));
|
||||
const serviceName = service.name ?? '';
|
||||
setSlashedPipelineName([
|
||||
{
|
||||
@ -345,11 +200,6 @@ const PipelineDetailsPage = () => {
|
||||
id: id,
|
||||
});
|
||||
|
||||
setPipelineUrl(pipelineUrl);
|
||||
setTasks(tasks || []);
|
||||
|
||||
setPipelineStatus(pipelineStatus as Pipeline['pipelineStatus']);
|
||||
|
||||
fetchServiceDetails(service.type, service.name ?? '')
|
||||
.then((hostPort: string) => {
|
||||
setPipelineUrl(hostPort + pipelineUrl);
|
||||
@ -384,30 +234,6 @@ const PipelineDetailsPage = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const fetchTabSpecificData = (tabField = '') => {
|
||||
switch (tabField) {
|
||||
case TabSpecificField.LINEAGE: {
|
||||
if (!deleted) {
|
||||
if (isEmpty(entityLineage)) {
|
||||
getLineageData();
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case TabSpecificField.ACTIVITY_FEED: {
|
||||
getFeedData();
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
const followPipeline = () => {
|
||||
addFollower(pipelineId, USERId)
|
||||
.then((res) => {
|
||||
@ -452,11 +278,7 @@ const PipelineDetailsPage = () => {
|
||||
try {
|
||||
const response = await saveUpdatedPipelineData(updatedPipeline);
|
||||
if (response) {
|
||||
const { description = '', version } = response;
|
||||
setCurrentVersion(version + '');
|
||||
setPipelineDetails(response);
|
||||
setDescription(description);
|
||||
getEntityFeedCount();
|
||||
} else {
|
||||
throw jsonData['api-error-messages']['unexpected-server-response'];
|
||||
}
|
||||
@ -471,10 +293,7 @@ const PipelineDetailsPage = () => {
|
||||
.then((res) => {
|
||||
if (res) {
|
||||
setPipelineDetails({ ...res, tags: res.tags ?? [] });
|
||||
setCurrentVersion(res.version + '');
|
||||
setOwner(res.owner);
|
||||
setTier(getTierTags(res.tags ?? []));
|
||||
getEntityFeedCount();
|
||||
|
||||
resolve();
|
||||
} else {
|
||||
throw jsonData['api-error-messages']['unexpected-server-response'];
|
||||
@ -495,10 +314,6 @@ const PipelineDetailsPage = () => {
|
||||
.then((res) => {
|
||||
if (res) {
|
||||
setPipelineDetails(res);
|
||||
setTier(getTierTags(res.tags ?? []));
|
||||
setCurrentVersion(res.version + '');
|
||||
setTags(getTagsWithoutTier(res.tags ?? []));
|
||||
getEntityFeedCount();
|
||||
} else {
|
||||
throw jsonData['api-error-messages']['unexpected-server-response'];
|
||||
}
|
||||
@ -517,7 +332,6 @@ const PipelineDetailsPage = () => {
|
||||
|
||||
if (response) {
|
||||
setTasks(response.tasks || []);
|
||||
getEntityFeedCount();
|
||||
} else {
|
||||
throw jsonData['api-error-messages']['unexpected-server-response'];
|
||||
}
|
||||
@ -605,83 +419,12 @@ const PipelineDetailsPage = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const postFeedHandler = (value: string, id: string) => {
|
||||
const currentUser = AppState.userDetails?.name ?? AppState.users[0]?.name;
|
||||
|
||||
const data = {
|
||||
message: value,
|
||||
from: currentUser,
|
||||
} as Post;
|
||||
postFeedById(id, data)
|
||||
.then((res) => {
|
||||
if (res) {
|
||||
const { id, posts } = res;
|
||||
setEntityThread((pre) => {
|
||||
return pre.map((thread) => {
|
||||
if (thread.id === id) {
|
||||
return { ...res, posts: posts?.slice(-3) };
|
||||
} else {
|
||||
return thread;
|
||||
}
|
||||
});
|
||||
});
|
||||
getEntityFeedCount();
|
||||
} else {
|
||||
throw jsonData['api-error-messages']['unexpected-server-response'];
|
||||
}
|
||||
})
|
||||
.catch((err: AxiosError) => {
|
||||
showErrorToast(err, jsonData['api-error-messages']['add-feed-error']);
|
||||
});
|
||||
};
|
||||
|
||||
const createThread = (data: CreateThread) => {
|
||||
postThread(data)
|
||||
.then((res) => {
|
||||
if (res) {
|
||||
setEntityThread((pre) => [...pre, res]);
|
||||
getEntityFeedCount();
|
||||
} else {
|
||||
showErrorToast(
|
||||
jsonData['api-error-messages']['unexpected-server-response']
|
||||
);
|
||||
}
|
||||
})
|
||||
.catch((err: AxiosError) => {
|
||||
showErrorToast(
|
||||
err,
|
||||
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 handleExtentionUpdate = async (updatedPipeline: Pipeline) => {
|
||||
const handleExtensionUpdate = async (updatedPipeline: Pipeline) => {
|
||||
try {
|
||||
const data = await saveUpdatedPipelineData(updatedPipeline);
|
||||
|
||||
if (data) {
|
||||
const { version, owner: ownerValue, tags = [] } = data;
|
||||
setCurrentVersion(version + '');
|
||||
setPipelineDetails(data);
|
||||
setOwner(ownerValue);
|
||||
setTier(getTierTags(tags));
|
||||
} else {
|
||||
throw jsonData['api-error-messages']['update-entity-error'];
|
||||
}
|
||||
@ -693,15 +436,10 @@ const PipelineDetailsPage = () => {
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchTabSpecificData(pipelineDetailsTabs[activeTab - 1].field);
|
||||
}, [activeTab]);
|
||||
|
||||
useEffect(() => {
|
||||
if (pipelinePermissions.ViewAll || pipelinePermissions.ViewBasic) {
|
||||
fetchPipelineDetail(pipelineFQN);
|
||||
setEntityLineage({} as EntityLineage);
|
||||
getEntityFeedCount();
|
||||
}
|
||||
}, [pipelinePermissions, pipelineFQN]);
|
||||
|
||||
@ -709,13 +447,6 @@ const PipelineDetailsPage = () => {
|
||||
fetchResourcePermission(pipelineFQN);
|
||||
}, [pipelineFQN]);
|
||||
|
||||
useEffect(() => {
|
||||
if (pipelineDetailsTabs[activeTab - 1].path !== tab) {
|
||||
setActiveTab(getCurrentPipelineTab(tab));
|
||||
}
|
||||
setEntityThread([]);
|
||||
}, [tab]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{isLoading ? (
|
||||
@ -728,50 +459,29 @@ const PipelineDetailsPage = () => {
|
||||
<>
|
||||
{pipelinePermissions.ViewAll || pipelinePermissions.ViewBasic ? (
|
||||
<PipelineDetails
|
||||
activeTab={activeTab}
|
||||
addLineageHandler={addLineageHandler}
|
||||
createThread={createThread}
|
||||
deletePostHandler={deletePostHandler}
|
||||
deleted={deleted}
|
||||
description={description}
|
||||
descriptionUpdateHandler={descriptionUpdateHandler}
|
||||
entityFieldTaskCount={entityFieldTaskCount}
|
||||
entityFieldThreadCount={entityFieldThreadCount}
|
||||
entityLineage={entityLineage}
|
||||
entityLineageHandler={entityLineageHandler}
|
||||
entityName={displayName}
|
||||
entityThread={entityThread}
|
||||
feedCount={feedCount}
|
||||
fetchFeedHandler={handleFeedFetchFromFeedList}
|
||||
followPipelineHandler={followPipeline}
|
||||
followers={followers}
|
||||
isLineageLoading={isLineageLoading}
|
||||
isNodeLoading={isNodeLoading}
|
||||
isentityThreadLoading={isentityThreadLoading}
|
||||
lineageLeafNodes={leafNodes}
|
||||
loadNodeHandler={loadNodeHandler}
|
||||
owner={owner as EntityReference}
|
||||
paging={paging}
|
||||
pipelineDetails={pipelineDetails}
|
||||
pipelineFQN={pipelineFQN}
|
||||
pipelineStatus={pipeLineStatus}
|
||||
pipelineTags={tags}
|
||||
pipelineUrl={pipelineUrl}
|
||||
postFeedHandler={postFeedHandler}
|
||||
removeLineageHandler={removeLineageHandler}
|
||||
serviceType={serviceType}
|
||||
setActiveTabHandler={activeTabHandler}
|
||||
settingsUpdateHandler={settingsUpdateHandler}
|
||||
slashedPipelineName={slashedPipelineName}
|
||||
tagUpdateHandler={onTagUpdate}
|
||||
taskUpdateHandler={onTaskUpdate}
|
||||
tasks={tasks}
|
||||
tier={tier as TagLabel}
|
||||
unfollowPipelineHandler={unfollowPipeline}
|
||||
updateThreadHandler={updateThreadHandler}
|
||||
version={currentVersion as string}
|
||||
versionHandler={versionHandler}
|
||||
onExtensionUpdate={handleExtentionUpdate}
|
||||
onExtensionUpdate={handleExtensionUpdate}
|
||||
/>
|
||||
) : (
|
||||
<ErrorPlaceHolder>{NO_PERMISSION_TO_VIEW}</ErrorPlaceHolder>
|
||||
|
@ -190,7 +190,7 @@ const TourPage = () => {
|
||||
fetchFeedHandler={handleCountChange}
|
||||
followTableHandler={handleCountChange}
|
||||
followers={mockDatasetData.followers}
|
||||
handleExtentionUpdate={handleCountChange}
|
||||
handleExtensionUpdate={handleCountChange}
|
||||
isNodeLoading={{
|
||||
id: undefined,
|
||||
state: false,
|
||||
|
@ -142,6 +142,9 @@
|
||||
.h-9 {
|
||||
height: 36px;
|
||||
}
|
||||
.h-100 {
|
||||
height: 400px;
|
||||
}
|
||||
.h-min-100 {
|
||||
min-height: 100vh;
|
||||
}
|
||||
@ -156,6 +159,10 @@
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.h-min-full {
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
// Text alignment
|
||||
.text-left {
|
||||
text-align: left;
|
||||
@ -280,6 +287,9 @@
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.bg-white {
|
||||
background: @white;
|
||||
}
|
||||
.font-semibold {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
@ -14,3 +14,9 @@
|
||||
.ant-tabs-tab.ant-tabs-tab-active {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.ant-tabs.ant-tabs-top.h-full {
|
||||
.ant-tabs-content.ant-tabs-content-top {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
@ -194,6 +194,9 @@
|
||||
.mr-8 {
|
||||
margin-right: 2rem;
|
||||
}
|
||||
.mb-2 {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.mb-4 {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
@ -231,9 +234,6 @@
|
||||
.m-52 {
|
||||
margin: 13rem;
|
||||
}
|
||||
.mr-2 {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.my-4 {
|
||||
margin-top: 1rem;
|
||||
|
@ -1,3 +1,16 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
.ant-tree-switcher {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
@ -42,3 +55,13 @@
|
||||
.ant-tree-switcher-icon {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.execution-node-container {
|
||||
overflow-x: auto;
|
||||
white-space: nowrap;
|
||||
overflow-y: hidden;
|
||||
}
|
||||
|
||||
.execution-node-container::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
@ -16,8 +16,10 @@ import { flatten, isEmpty } from 'lodash';
|
||||
import { Bucket, EntityTags, TableColumn, TagOption } from 'Models';
|
||||
import { getCategory, getTags } from '../axiosAPIs/tagAPI';
|
||||
import { TAG_VIEW_CAP } from '../constants/constants';
|
||||
import { SettledStatus } from '../enums/axios.enum';
|
||||
import { TagCategory, TagClass } from '../generated/entity/tags/tagCategory';
|
||||
import { LabelType, State, TagSource } from '../generated/type/tagLabel';
|
||||
import { fetchGlossaryTerms, getGlossaryTermlist } from './GlossaryUtils';
|
||||
|
||||
export const getTagCategories = async (fields?: Array<string> | string) => {
|
||||
try {
|
||||
@ -118,3 +120,34 @@ export const getTagsWithLabel = (tags: Array<Bucket>) => {
|
||||
export const getTagDisplay = (tag: string) => {
|
||||
return tag.length > TAG_VIEW_CAP ? `${tag.slice(0, TAG_VIEW_CAP)}...` : tag;
|
||||
};
|
||||
|
||||
export const fetchTagsAndGlossaryTerms = async () => {
|
||||
const responses = await Promise.allSettled([
|
||||
getTagCategories(),
|
||||
fetchGlossaryTerms(),
|
||||
]);
|
||||
|
||||
let tagsAndTerms: TagOption[] = [];
|
||||
if (
|
||||
responses[0].status === SettledStatus.FULFILLED &&
|
||||
responses[0].value.data
|
||||
) {
|
||||
tagsAndTerms = getTaglist(responses[0].value.data).map((tag) => {
|
||||
return { fqn: tag, source: 'Tag' };
|
||||
});
|
||||
}
|
||||
if (
|
||||
responses[1].status === SettledStatus.FULFILLED &&
|
||||
responses[1].value &&
|
||||
responses[1].value.length > 0
|
||||
) {
|
||||
const glossaryTerms: TagOption[] = getGlossaryTermlist(
|
||||
responses[1].value
|
||||
).map((tag) => {
|
||||
return { fqn: tag, source: 'Glossary' };
|
||||
});
|
||||
tagsAndTerms = [...tagsAndTerms, ...glossaryTerms];
|
||||
}
|
||||
|
||||
return tagsAndTerms;
|
||||
};
|
||||
|
Loading…
x
Reference in New Issue
Block a user