mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-09-11 09:59:04 +00:00
Added pipeline entity page (#690)
* Added pipeline entity page * added support for pipeline in recentlyviewed data. * added pipeline index ij suggestion api.
This commit is contained in:
parent
df1c197e1f
commit
145b3283c2
@ -59,7 +59,7 @@ export const getSuggestions: Function = (
|
|||||||
queryString: string
|
queryString: string
|
||||||
): Promise<AxiosResponse> => {
|
): Promise<AxiosResponse> => {
|
||||||
return APIClient.get(
|
return APIClient.get(
|
||||||
`/search/suggest?q=${queryString}&index=${SearchIndex.DASHBOARD},${SearchIndex.TABLE},${SearchIndex.TOPIC}
|
`/search/suggest?q=${queryString}&index=${SearchIndex.DASHBOARD},${SearchIndex.TABLE},${SearchIndex.TOPIC},${SearchIndex.PIPELINE}
|
||||||
`
|
`
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
import { AxiosResponse } from 'axios';
|
||||||
|
import { Task } from '../generated/entity/data/task';
|
||||||
|
import { getURLWithQueryFields } from '../utils/APIUtils';
|
||||||
|
import APIClient from './index';
|
||||||
|
|
||||||
|
export const getTaskById: Function = (
|
||||||
|
id: string,
|
||||||
|
arrQueryFields: string
|
||||||
|
): Promise<AxiosResponse> => {
|
||||||
|
const url = getURLWithQueryFields(`/tasks/${id}`, arrQueryFields);
|
||||||
|
|
||||||
|
return APIClient.get(url);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateTask: Function = (
|
||||||
|
id: string,
|
||||||
|
data: Task
|
||||||
|
): Promise<AxiosResponse> => {
|
||||||
|
const configOptions = {
|
||||||
|
headers: { 'Content-type': 'application/json-patch+json' },
|
||||||
|
};
|
||||||
|
|
||||||
|
return APIClient.patch(`/tasks/${id}`, data, configOptions);
|
||||||
|
};
|
@ -18,6 +18,7 @@
|
|||||||
import { ColumnTags, FormatedTableData } from 'Models';
|
import { ColumnTags, FormatedTableData } from 'Models';
|
||||||
import React, { FunctionComponent, useEffect, useState } from 'react';
|
import React, { FunctionComponent, useEffect, useState } from 'react';
|
||||||
import { getDashboardByFqn } from '../../axiosAPIs/dashboardAPI';
|
import { getDashboardByFqn } from '../../axiosAPIs/dashboardAPI';
|
||||||
|
import { getPipelineByFqn } from '../../axiosAPIs/pipelineAPI';
|
||||||
import { getTableDetailsByFQN } from '../../axiosAPIs/tableAPI';
|
import { getTableDetailsByFQN } from '../../axiosAPIs/tableAPI';
|
||||||
import { getTopicByFqn } from '../../axiosAPIs/topicsAPI';
|
import { getTopicByFqn } from '../../axiosAPIs/topicsAPI';
|
||||||
import { EntityType } from '../../enums/entity.enum';
|
import { EntityType } from '../../enums/entity.enum';
|
||||||
@ -121,6 +122,36 @@ const RecentlyViewed: FunctionComponent = () => {
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case EntityType.PIPELINE: {
|
||||||
|
const res = await getPipelineByFqn(
|
||||||
|
oData.fqn,
|
||||||
|
'owner, service, tags, usageSummary'
|
||||||
|
);
|
||||||
|
|
||||||
|
const {
|
||||||
|
description,
|
||||||
|
id,
|
||||||
|
displayName,
|
||||||
|
tags,
|
||||||
|
owner,
|
||||||
|
fullyQualifiedName,
|
||||||
|
} = res.data;
|
||||||
|
arrData.push({
|
||||||
|
description,
|
||||||
|
fullyQualifiedName,
|
||||||
|
id,
|
||||||
|
index: SearchIndex.PIPELINE,
|
||||||
|
name: displayName,
|
||||||
|
owner: getOwnerFromId(owner?.id)?.name || '--',
|
||||||
|
serviceType: oData.serviceType,
|
||||||
|
tags: (tags as Array<ColumnTags>).map((tag) => tag.tagFQN),
|
||||||
|
tier: getTierFromTableTags(tags as Array<ColumnTags>),
|
||||||
|
});
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,7 @@ export const ERROR404 = 'No data found';
|
|||||||
export const ERROR500 = 'Something went wrong';
|
export const ERROR500 = 'Something went wrong';
|
||||||
const PLACEHOLDER_ROUTE_DATASET_FQN = ':datasetFQN';
|
const PLACEHOLDER_ROUTE_DATASET_FQN = ':datasetFQN';
|
||||||
const PLACEHOLDER_ROUTE_TOPIC_FQN = ':topicFQN';
|
const PLACEHOLDER_ROUTE_TOPIC_FQN = ':topicFQN';
|
||||||
|
const PLACEHOLDER_ROUTE_PIPELINE_FQN = ':pipelineFQN';
|
||||||
const PLACEHOLDER_ROUTE_DASHBOARD_FQN = ':dashboardFQN';
|
const PLACEHOLDER_ROUTE_DASHBOARD_FQN = ':dashboardFQN';
|
||||||
const PLACEHOLDER_ROUTE_DATABASE_FQN = ':databaseFQN';
|
const PLACEHOLDER_ROUTE_DATABASE_FQN = ':databaseFQN';
|
||||||
const PLACEHOLDER_ROUTE_SERVICE_FQN = ':serviceFQN';
|
const PLACEHOLDER_ROUTE_SERVICE_FQN = ':serviceFQN';
|
||||||
@ -121,6 +122,7 @@ export const ROUTES = {
|
|||||||
TOPIC_DETAILS: `/topic/${PLACEHOLDER_ROUTE_TOPIC_FQN}`,
|
TOPIC_DETAILS: `/topic/${PLACEHOLDER_ROUTE_TOPIC_FQN}`,
|
||||||
DASHBOARD_DETAILS: `/dashboard/${PLACEHOLDER_ROUTE_DASHBOARD_FQN}`,
|
DASHBOARD_DETAILS: `/dashboard/${PLACEHOLDER_ROUTE_DASHBOARD_FQN}`,
|
||||||
DATABASE_DETAILS: `/database/${PLACEHOLDER_ROUTE_DATABASE_FQN}`,
|
DATABASE_DETAILS: `/database/${PLACEHOLDER_ROUTE_DATABASE_FQN}`,
|
||||||
|
PIPELINE_DETAILS: `/pipeline/${PLACEHOLDER_ROUTE_PIPELINE_FQN}`,
|
||||||
ONBOARDING: '/onboarding',
|
ONBOARDING: '/onboarding',
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -178,6 +180,12 @@ export const getDashboardDetailsPath = (dashboardFQN: string) => {
|
|||||||
|
|
||||||
return path;
|
return path;
|
||||||
};
|
};
|
||||||
|
export const getPipelineDetailsPath = (pipelineFQN: string) => {
|
||||||
|
let path = ROUTES.PIPELINE_DETAILS;
|
||||||
|
path = path.replace(PLACEHOLDER_ROUTE_PIPELINE_FQN, pipelineFQN);
|
||||||
|
|
||||||
|
return path;
|
||||||
|
};
|
||||||
|
|
||||||
export const LIST_TYPES = ['numbered-list', 'bulleted-list'];
|
export const LIST_TYPES = ['numbered-list', 'bulleted-list'];
|
||||||
|
|
||||||
|
@ -19,4 +19,5 @@ export enum EntityType {
|
|||||||
DATASET = 'dataset',
|
DATASET = 'dataset',
|
||||||
TOPIC = 'topic',
|
TOPIC = 'topic',
|
||||||
DASHBOARD = 'dashboard',
|
DASHBOARD = 'dashboard',
|
||||||
|
PIPELINE = 'pipeline',
|
||||||
}
|
}
|
||||||
|
@ -367,7 +367,7 @@ declare module 'Models' {
|
|||||||
// topic interface end
|
// topic interface end
|
||||||
|
|
||||||
interface RecentlyViewedData {
|
interface RecentlyViewedData {
|
||||||
entityType: 'dataset' | 'topic' | 'dashboard';
|
entityType: 'dataset' | 'topic' | 'dashboard' | 'pipeline';
|
||||||
fqn: string;
|
fqn: string;
|
||||||
serviceType?: string;
|
serviceType?: string;
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
|
@ -0,0 +1,606 @@
|
|||||||
|
import { AxiosPromise, AxiosResponse } from 'axios';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
import { compare } from 'fast-json-patch';
|
||||||
|
import { ColumnTags, TableDetail } from 'Models';
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { Link, useParams } from 'react-router-dom';
|
||||||
|
import AppState from '../../AppState';
|
||||||
|
import {
|
||||||
|
addFollower,
|
||||||
|
getPipelineByFqn,
|
||||||
|
patchPipelineDetails,
|
||||||
|
removeFollower,
|
||||||
|
} from '../../axiosAPIs/pipelineAPI';
|
||||||
|
import { getServiceById } from '../../axiosAPIs/serviceAPI';
|
||||||
|
import { getTaskById, updateTask } from '../../axiosAPIs/taskAPI';
|
||||||
|
import Description from '../../components/common/description/Description';
|
||||||
|
import EntityPageInfo from '../../components/common/entityPageInfo/EntityPageInfo';
|
||||||
|
import NonAdminAction from '../../components/common/non-admin-action/NonAdminAction';
|
||||||
|
import RichTextEditorPreviewer from '../../components/common/rich-text-editor/RichTextEditorPreviewer';
|
||||||
|
import TabsPane from '../../components/common/TabsPane/TabsPane';
|
||||||
|
import { TitleBreadcrumbProps } from '../../components/common/title-breadcrumb/title-breadcrumb.interface';
|
||||||
|
import PageContainer from '../../components/containers/PageContainer';
|
||||||
|
import Loader from '../../components/Loader/Loader';
|
||||||
|
import { ModalWithMarkdownEditor } from '../../components/Modals/ModalWithMarkdownEditor/ModalWithMarkdownEditor';
|
||||||
|
import ManageTab from '../../components/my-data-details/ManageTab';
|
||||||
|
import TagsContainer from '../../components/tags-container/tags-container';
|
||||||
|
import Tags from '../../components/tags/tags';
|
||||||
|
import { getServiceDetailsPath } from '../../constants/constants';
|
||||||
|
import { EntityType } from '../../enums/entity.enum';
|
||||||
|
import { Pipeline } from '../../generated/entity/data/pipeline';
|
||||||
|
import { Task } from '../../generated/entity/data/task';
|
||||||
|
import { User } from '../../generated/entity/teams/user';
|
||||||
|
import { TagLabel } from '../../generated/type/tagLabel';
|
||||||
|
import { useAuth } from '../../hooks/authHooks';
|
||||||
|
import {
|
||||||
|
addToRecentViewed,
|
||||||
|
getCurrentUserId,
|
||||||
|
getHtmlForNonAdminAction,
|
||||||
|
getUserTeams,
|
||||||
|
isEven,
|
||||||
|
} from '../../utils/CommonUtils';
|
||||||
|
import { serviceTypeLogo } from '../../utils/ServiceUtils';
|
||||||
|
import SVGIcons from '../../utils/SvgUtils';
|
||||||
|
import {
|
||||||
|
getOwnerFromId,
|
||||||
|
getTagsWithoutTier,
|
||||||
|
getTierFromTableTags,
|
||||||
|
} from '../../utils/TableUtils';
|
||||||
|
import { getTagCategories, getTaglist } from '../../utils/TagsUtils';
|
||||||
|
|
||||||
|
const MyPipelinePage = () => {
|
||||||
|
const USERId = getCurrentUserId();
|
||||||
|
|
||||||
|
const { isAuthDisabled } = useAuth();
|
||||||
|
|
||||||
|
const [tagList, setTagList] = useState<Array<string>>([]);
|
||||||
|
const { pipelineFQN } = useParams() as Record<string, string>;
|
||||||
|
const [pipelineDetails, setPipelineDetails] = useState<Pipeline>(
|
||||||
|
{} as Pipeline
|
||||||
|
);
|
||||||
|
const [pipelineId, setPipelineId] = useState<string>('');
|
||||||
|
const [isLoading, setLoading] = useState<boolean>(false);
|
||||||
|
const [description, setDescription] = useState<string>('');
|
||||||
|
const [followers, setFollowers] = useState<Array<User>>([]);
|
||||||
|
const [followersCount, setFollowersCount] = useState<number>(0);
|
||||||
|
const [isFollowing, setIsFollowing] = useState(false);
|
||||||
|
const [owner, setOwner] = useState<TableDetail['owner']>();
|
||||||
|
const [tier, setTier] = useState<string>();
|
||||||
|
const [tags, setTags] = useState<Array<ColumnTags>>([]);
|
||||||
|
const [activeTab, setActiveTab] = useState<number>(1);
|
||||||
|
const [isEdit, setIsEdit] = useState<boolean>(false);
|
||||||
|
const [tasks, setTasks] = useState<Task[]>([]);
|
||||||
|
const [pipelineUrl, setPipelineUrl] = useState<string>('');
|
||||||
|
const [displayName, setDisplayName] = useState<string>('');
|
||||||
|
// const [usage, setUsage] = useState('');
|
||||||
|
// const [weeklyUsageCount, setWeeklyUsageCount] = useState('');
|
||||||
|
const [slashedPipelineName, setSlashedPipelineName] = useState<
|
||||||
|
TitleBreadcrumbProps['titleLinks']
|
||||||
|
>([]);
|
||||||
|
|
||||||
|
const [editTask, setEditTask] = useState<{
|
||||||
|
task: Task;
|
||||||
|
index: number;
|
||||||
|
}>();
|
||||||
|
const [editTaskTags, setEditTaskTags] = useState<{
|
||||||
|
task: Task;
|
||||||
|
index: number;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const hasEditAccess = () => {
|
||||||
|
if (owner?.type === 'user') {
|
||||||
|
return owner.id === getCurrentUserId();
|
||||||
|
} else {
|
||||||
|
return getUserTeams().some((team) => team.id === owner?.id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const tabs = [
|
||||||
|
{
|
||||||
|
name: 'Details',
|
||||||
|
icon: {
|
||||||
|
alt: 'schema',
|
||||||
|
name: 'icon-schema',
|
||||||
|
title: 'Details',
|
||||||
|
},
|
||||||
|
isProtected: false,
|
||||||
|
position: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Manage',
|
||||||
|
icon: {
|
||||||
|
alt: 'manage',
|
||||||
|
name: 'icon-manage',
|
||||||
|
title: 'Manage',
|
||||||
|
},
|
||||||
|
isProtected: true,
|
||||||
|
protectedState: !owner || hasEditAccess(),
|
||||||
|
position: 2,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const extraInfo = [
|
||||||
|
{ key: 'Owner', value: owner?.name || '' },
|
||||||
|
{ key: 'Tier', value: tier ? tier.split('.')[1] : '' },
|
||||||
|
{ key: 'Pipeline Url', value: pipelineUrl, isLink: true },
|
||||||
|
// { key: 'Usage', value: usage },
|
||||||
|
// { key: 'Queries', value: `${weeklyUsageCount} past week` },
|
||||||
|
];
|
||||||
|
const fetchTags = () => {
|
||||||
|
getTagCategories().then((res) => {
|
||||||
|
if (res.data) {
|
||||||
|
setTagList(getTaglist(res.data));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchTasks = async (tasks: Pipeline['tasks']) => {
|
||||||
|
let tasksData: Task[] = [];
|
||||||
|
let promiseArr: Array<AxiosPromise> = [];
|
||||||
|
if (tasks?.length) {
|
||||||
|
promiseArr = tasks.map((task) =>
|
||||||
|
getTaskById(task.id, ['service', 'tags'])
|
||||||
|
);
|
||||||
|
await Promise.allSettled(promiseArr).then(
|
||||||
|
(res: PromiseSettledResult<AxiosResponse>[]) => {
|
||||||
|
if (res.length) {
|
||||||
|
tasksData = res
|
||||||
|
.filter((task) => task.status === 'fulfilled')
|
||||||
|
.map(
|
||||||
|
(task) =>
|
||||||
|
(task as PromiseFulfilledResult<AxiosResponse>).value.data
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tasksData;
|
||||||
|
};
|
||||||
|
|
||||||
|
const setFollowersData = (followers: Array<User>) => {
|
||||||
|
// need to check if already following or not with logedIn user id
|
||||||
|
setIsFollowing(followers.some(({ id }: { id: string }) => id === USERId));
|
||||||
|
setFollowersCount(followers?.length);
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchPipelineDetail = (pipelineFQN: string) => {
|
||||||
|
setLoading(true);
|
||||||
|
getPipelineByFqn(pipelineFQN, [
|
||||||
|
'owner',
|
||||||
|
'service',
|
||||||
|
'followers',
|
||||||
|
'tags',
|
||||||
|
'usageSummary',
|
||||||
|
'tasks',
|
||||||
|
]).then((res: AxiosResponse) => {
|
||||||
|
const {
|
||||||
|
id,
|
||||||
|
description,
|
||||||
|
followers,
|
||||||
|
fullyQualifiedName,
|
||||||
|
service,
|
||||||
|
tags,
|
||||||
|
owner,
|
||||||
|
displayName,
|
||||||
|
tasks,
|
||||||
|
pipelineUrl,
|
||||||
|
// usageSummary,
|
||||||
|
} = res.data;
|
||||||
|
setDisplayName(displayName);
|
||||||
|
setPipelineDetails(res.data);
|
||||||
|
setPipelineId(id);
|
||||||
|
setDescription(description ?? '');
|
||||||
|
setFollowers(followers);
|
||||||
|
setFollowersData(followers);
|
||||||
|
setOwner(getOwnerFromId(owner?.id));
|
||||||
|
setTier(getTierFromTableTags(tags));
|
||||||
|
setTags(getTagsWithoutTier(tags));
|
||||||
|
getServiceById('pipelineServices', service?.id).then(
|
||||||
|
(serviceRes: AxiosResponse) => {
|
||||||
|
setSlashedPipelineName([
|
||||||
|
{
|
||||||
|
name: serviceRes.data.name,
|
||||||
|
url: serviceRes.data.name
|
||||||
|
? getServiceDetailsPath(
|
||||||
|
serviceRes.data.name,
|
||||||
|
serviceRes.data.serviceType
|
||||||
|
)
|
||||||
|
: '',
|
||||||
|
imgSrc: serviceRes.data.serviceType
|
||||||
|
? serviceTypeLogo(serviceRes.data.serviceType)
|
||||||
|
: undefined,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: displayName,
|
||||||
|
url: '',
|
||||||
|
activeTitle: true,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
addToRecentViewed({
|
||||||
|
entityType: EntityType.PIPELINE,
|
||||||
|
fqn: fullyQualifiedName,
|
||||||
|
serviceType: serviceRes.data.serviceType,
|
||||||
|
timestamp: 0,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
setPipelineUrl(pipelineUrl);
|
||||||
|
fetchTasks(tasks).then((tasks) => setTasks(tasks));
|
||||||
|
// if (!isNil(usageSummary?.weeklyStats.percentileRank)) {
|
||||||
|
// const percentile = getUsagePercentile(
|
||||||
|
// usageSummary.weeklyStats.percentileRank
|
||||||
|
// );
|
||||||
|
// setUsage(percentile);
|
||||||
|
// } else {
|
||||||
|
// setUsage('--');
|
||||||
|
// }
|
||||||
|
// setWeeklyUsageCount(
|
||||||
|
// usageSummary?.weeklyStats.count.toLocaleString() || '--'
|
||||||
|
// );
|
||||||
|
|
||||||
|
setLoading(false);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const followPipeline = (): void => {
|
||||||
|
if (isFollowing) {
|
||||||
|
removeFollower(pipelineId, USERId).then((res: AxiosResponse) => {
|
||||||
|
const { followers } = res.data;
|
||||||
|
setFollowers(followers);
|
||||||
|
setFollowersCount((preValu) => preValu - 1);
|
||||||
|
setIsFollowing(false);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
addFollower(pipelineId, USERId).then((res: AxiosResponse) => {
|
||||||
|
const { followers } = res.data;
|
||||||
|
setFollowers(followers);
|
||||||
|
setFollowersCount((preValu) => preValu + 1);
|
||||||
|
setIsFollowing(true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDescriptionUpdate = (updatedHTML: string) => {
|
||||||
|
const updatedPipeline = { ...pipelineDetails, description: updatedHTML };
|
||||||
|
|
||||||
|
const jsonPatch = compare(pipelineDetails, updatedPipeline);
|
||||||
|
patchPipelineDetails(pipelineId, jsonPatch).then((res: AxiosResponse) => {
|
||||||
|
setDescription(res.data.description);
|
||||||
|
});
|
||||||
|
setIsEdit(false);
|
||||||
|
};
|
||||||
|
const onDescriptionEdit = (): void => {
|
||||||
|
setIsEdit(true);
|
||||||
|
};
|
||||||
|
const onCancel = () => {
|
||||||
|
setIsEdit(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSettingsUpdate = (
|
||||||
|
newOwner?: TableDetail['owner'],
|
||||||
|
newTier?: TableDetail['tier']
|
||||||
|
): Promise<void> => {
|
||||||
|
return new Promise<void>((resolve, reject) => {
|
||||||
|
if (newOwner || newTier) {
|
||||||
|
const tierTag: TableDetail['tags'] = newTier
|
||||||
|
? [
|
||||||
|
...getTagsWithoutTier(pipelineDetails.tags as ColumnTags[]),
|
||||||
|
{ tagFQN: newTier, labelType: 'Manual', state: 'Confirmed' },
|
||||||
|
]
|
||||||
|
: (pipelineDetails.tags as ColumnTags[]);
|
||||||
|
const updatedPipeline = {
|
||||||
|
...pipelineDetails,
|
||||||
|
owner: newOwner
|
||||||
|
? { ...pipelineDetails.owner, ...newOwner }
|
||||||
|
: pipelineDetails.owner,
|
||||||
|
tags: tierTag,
|
||||||
|
};
|
||||||
|
const jsonPatch = compare(pipelineDetails, updatedPipeline);
|
||||||
|
patchPipelineDetails(pipelineId, jsonPatch)
|
||||||
|
.then((res: AxiosResponse) => {
|
||||||
|
setPipelineDetails(res.data);
|
||||||
|
setOwner(getOwnerFromId(res.data.owner?.id));
|
||||||
|
setTier(getTierFromTableTags(res.data.tags));
|
||||||
|
resolve();
|
||||||
|
})
|
||||||
|
.catch(() => reject());
|
||||||
|
} else {
|
||||||
|
reject();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onTagUpdate = (selectedTags?: Array<string>) => {
|
||||||
|
if (selectedTags) {
|
||||||
|
const prevTags = pipelineDetails?.tags?.filter((tag) =>
|
||||||
|
selectedTags.includes(tag?.tagFQN as string)
|
||||||
|
);
|
||||||
|
const newTags: Array<ColumnTags> = selectedTags
|
||||||
|
.filter((tag) => {
|
||||||
|
return !prevTags?.map((prevTag) => prevTag.tagFQN).includes(tag);
|
||||||
|
})
|
||||||
|
.map((tag) => ({
|
||||||
|
labelType: 'Manual',
|
||||||
|
state: 'Confirmed',
|
||||||
|
tagFQN: tag,
|
||||||
|
}));
|
||||||
|
const updatedTags = [...(prevTags as TagLabel[]), ...newTags];
|
||||||
|
const updatedPipeline = { ...pipelineDetails, tags: updatedTags };
|
||||||
|
const jsonPatch = compare(pipelineDetails, updatedPipeline);
|
||||||
|
patchPipelineDetails(pipelineId, jsonPatch).then((res: AxiosResponse) => {
|
||||||
|
setTier(getTierFromTableTags(res.data.tags));
|
||||||
|
setTags(getTagsWithoutTier(res.data.tags));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUpdateTask = (task: Task, index: number) => {
|
||||||
|
setEditTask({ task, index });
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeEditTaskModal = (): void => {
|
||||||
|
setEditTask(undefined);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onTaskUpdate = (taskDescription: string) => {
|
||||||
|
if (editTask) {
|
||||||
|
const updatedTask = {
|
||||||
|
...editTask.task,
|
||||||
|
description: taskDescription,
|
||||||
|
};
|
||||||
|
const jsonPatch = compare(tasks[editTask.index], updatedTask);
|
||||||
|
updateTask(editTask.task.id, jsonPatch).then((res: AxiosResponse) => {
|
||||||
|
if (res.data) {
|
||||||
|
setTasks((prevTasks) => {
|
||||||
|
const tasks = [...prevTasks];
|
||||||
|
tasks[editTask.index] = res.data;
|
||||||
|
|
||||||
|
return tasks;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setEditTask(undefined);
|
||||||
|
} else {
|
||||||
|
setEditTask(undefined);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleEditTaskTag = (task: Task, index: number): void => {
|
||||||
|
setEditTaskTags({ task, index });
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleTaskTagSelection = (selectedTags?: Array<ColumnTags>) => {
|
||||||
|
if (selectedTags && editTaskTags) {
|
||||||
|
const prevTags = editTaskTags.task.tags?.filter((tag) =>
|
||||||
|
selectedTags.some((selectedTag) => selectedTag.tagFQN === tag.tagFQN)
|
||||||
|
);
|
||||||
|
const newTags = selectedTags
|
||||||
|
.filter(
|
||||||
|
(selectedTag) =>
|
||||||
|
!editTaskTags.task.tags?.some(
|
||||||
|
(tag) => tag.tagFQN === selectedTag.tagFQN
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.map((tag) => ({
|
||||||
|
labelType: 'Manual',
|
||||||
|
state: 'Confirmed',
|
||||||
|
tagFQN: tag.tagFQN,
|
||||||
|
}));
|
||||||
|
|
||||||
|
const updatedTask = {
|
||||||
|
...editTaskTags.task,
|
||||||
|
tags: [...(prevTags as TagLabel[]), ...newTags],
|
||||||
|
};
|
||||||
|
const jsonPatch = compare(tasks[editTaskTags.index], updatedTask);
|
||||||
|
updateTask(editTaskTags.task.id, jsonPatch).then((res: AxiosResponse) => {
|
||||||
|
if (res.data) {
|
||||||
|
setTasks((prevTasks) => {
|
||||||
|
const tasks = [...prevTasks];
|
||||||
|
tasks[editTaskTags.index] = res.data;
|
||||||
|
|
||||||
|
return tasks;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setEditTaskTags(undefined);
|
||||||
|
} else {
|
||||||
|
setEditTaskTags(undefined);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchPipelineDetail(pipelineFQN);
|
||||||
|
}, [pipelineFQN]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isAuthDisabled && AppState.users.length && followers.length) {
|
||||||
|
setFollowersData(followers);
|
||||||
|
}
|
||||||
|
}, [AppState.users, followers]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchTags();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<PageContainer>
|
||||||
|
{isLoading ? (
|
||||||
|
<Loader />
|
||||||
|
) : (
|
||||||
|
<div className="tw-px-4 w-full">
|
||||||
|
<EntityPageInfo
|
||||||
|
isTagEditable
|
||||||
|
entityName={displayName}
|
||||||
|
extraInfo={extraInfo}
|
||||||
|
followers={followersCount}
|
||||||
|
followersList={followers}
|
||||||
|
followHandler={followPipeline}
|
||||||
|
hasEditAccess={hasEditAccess()}
|
||||||
|
isFollowing={isFollowing}
|
||||||
|
owner={owner}
|
||||||
|
tagList={tagList}
|
||||||
|
tags={tags}
|
||||||
|
tagsHandler={onTagUpdate}
|
||||||
|
tier={tier || ''}
|
||||||
|
titleLinks={slashedPipelineName}
|
||||||
|
/>
|
||||||
|
<div className="tw-block tw-mt-1">
|
||||||
|
<TabsPane
|
||||||
|
activeTab={activeTab}
|
||||||
|
setActiveTab={setActiveTab}
|
||||||
|
tabs={tabs}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className="tw-bg-white tw--mx-4 tw-p-4">
|
||||||
|
{activeTab === 1 && (
|
||||||
|
<>
|
||||||
|
<div className="tw-grid tw-grid-cols-4 tw-gap-4 w-full">
|
||||||
|
<div className="tw-col-span-full">
|
||||||
|
<Description
|
||||||
|
description={description}
|
||||||
|
hasEditAccess={hasEditAccess()}
|
||||||
|
isEdit={isEdit}
|
||||||
|
owner={owner}
|
||||||
|
onCancel={onCancel}
|
||||||
|
onDescriptionEdit={onDescriptionEdit}
|
||||||
|
onDescriptionUpdate={onDescriptionUpdate}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="tw-table-responsive tw-my-6">
|
||||||
|
<table className="tw-w-full" data-testid="schema-table">
|
||||||
|
<thead>
|
||||||
|
<tr className="tableHead-row">
|
||||||
|
<th className="tableHead-cell">Task Name</th>
|
||||||
|
<th className="tableHead-cell">Description</th>
|
||||||
|
<th className="tableHead-cell tw-w-60">Tags</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 hover:tw-underline tw-flex"
|
||||||
|
data-testid="description"
|
||||||
|
onClick={() => handleUpdateTask(task, index)}>
|
||||||
|
<div>
|
||||||
|
{task.description ? (
|
||||||
|
<RichTextEditorPreviewer
|
||||||
|
markdown={task.description}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<span className="tw-no-description">
|
||||||
|
No description added
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<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">
|
||||||
|
<SVGIcons
|
||||||
|
alt="edit"
|
||||||
|
icon="icon-edit"
|
||||||
|
title="Edit"
|
||||||
|
width="10px"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td
|
||||||
|
className="tw-group tw-relative tableBody-cell"
|
||||||
|
onClick={() => {
|
||||||
|
if (!editTaskTags) {
|
||||||
|
handleEditTaskTag(task, index);
|
||||||
|
}
|
||||||
|
}}>
|
||||||
|
<NonAdminAction
|
||||||
|
html={getHtmlForNonAdminAction(Boolean(owner))}
|
||||||
|
isOwner={hasEditAccess()}
|
||||||
|
position="left"
|
||||||
|
trigger="click">
|
||||||
|
<TagsContainer
|
||||||
|
editable={editTaskTags?.index === index}
|
||||||
|
selectedTags={task.tags as ColumnTags[]}
|
||||||
|
tagList={tagList}
|
||||||
|
onCancel={() => {
|
||||||
|
handleTaskTagSelection();
|
||||||
|
}}
|
||||||
|
onSelectionChange={(tags) => {
|
||||||
|
handleTaskTagSelection(tags);
|
||||||
|
}}>
|
||||||
|
{task.tags?.length ? (
|
||||||
|
<button className="tw-opacity-0 tw-ml-1 group-hover:tw-opacity-100 focus:tw-outline-none">
|
||||||
|
<SVGIcons
|
||||||
|
alt="edit"
|
||||||
|
icon="icon-edit"
|
||||||
|
title="Edit"
|
||||||
|
width="10px"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
|
) : (
|
||||||
|
<span className="tw-opacity-0 group-hover:tw-opacity-100">
|
||||||
|
<Tags
|
||||||
|
className="tw-border-main"
|
||||||
|
tag="+ Add tag"
|
||||||
|
type="outlined"
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</TagsContainer>
|
||||||
|
</NonAdminAction>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{activeTab === 2 && (
|
||||||
|
<ManageTab
|
||||||
|
currentTier={tier}
|
||||||
|
currentUser={owner?.id}
|
||||||
|
hasEditAccess={hasEditAccess()}
|
||||||
|
onSave={onSettingsUpdate}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{editTask && (
|
||||||
|
<ModalWithMarkdownEditor
|
||||||
|
header={`Edit Task: "${editTask.task.displayName}"`}
|
||||||
|
placeholder="Enter Task Description"
|
||||||
|
value={editTask.task.description || ''}
|
||||||
|
onCancel={closeEditTaskModal}
|
||||||
|
onSave={onTaskUpdate}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</PageContainer>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default MyPipelinePage;
|
@ -207,6 +207,9 @@ const ServicePage: FunctionComponent = () => {
|
|||||||
case ServiceCategory.DASHBOARD_SERVICES:
|
case ServiceCategory.DASHBOARD_SERVICES:
|
||||||
return getEntityLink(SearchIndex.DASHBOARD, fqn);
|
return getEntityLink(SearchIndex.DASHBOARD, fqn);
|
||||||
|
|
||||||
|
case ServiceCategory.PIPELINE_SERVICES:
|
||||||
|
return getEntityLink(SearchIndex.PIPELINE, fqn);
|
||||||
|
|
||||||
case ServiceCategory.DATABASE_SERVICES:
|
case ServiceCategory.DATABASE_SERVICES:
|
||||||
default:
|
default:
|
||||||
return `/database/${fqn}`;
|
return `/database/${fqn}`;
|
||||||
|
@ -26,6 +26,7 @@ import DatabaseDetails from '../pages/database-details/index';
|
|||||||
import ExplorePage from '../pages/explore';
|
import ExplorePage from '../pages/explore';
|
||||||
import MyDataPage from '../pages/my-data';
|
import MyDataPage from '../pages/my-data';
|
||||||
import MyDataDetailsPage from '../pages/my-data-details';
|
import MyDataDetailsPage from '../pages/my-data-details';
|
||||||
|
import MyPipelinePage from '../pages/Pipeline-details';
|
||||||
import ReportsPage from '../pages/reports';
|
import ReportsPage from '../pages/reports';
|
||||||
import Scorecard from '../pages/scorecard';
|
import Scorecard from '../pages/scorecard';
|
||||||
import ServicePage from '../pages/service';
|
import ServicePage from '../pages/service';
|
||||||
@ -67,6 +68,7 @@ const AuthenticatedAppRouter: FunctionComponent = () => {
|
|||||||
<Route component={MyDataDetailsPage} path={ROUTES.DATASET_DETAILS} />
|
<Route component={MyDataDetailsPage} path={ROUTES.DATASET_DETAILS} />
|
||||||
<Route component={MyTopicDetailPage} path={ROUTES.TOPIC_DETAILS} />
|
<Route component={MyTopicDetailPage} path={ROUTES.TOPIC_DETAILS} />
|
||||||
<Route component={MyDashBoardPage} path={ROUTES.DASHBOARD_DETAILS} />
|
<Route component={MyDashBoardPage} path={ROUTES.DASHBOARD_DETAILS} />
|
||||||
|
<Route component={MyPipelinePage} path={ROUTES.PIPELINE_DETAILS} />
|
||||||
<Route component={Onboarding} path={ROUTES.ONBOARDING} />
|
<Route component={Onboarding} path={ROUTES.ONBOARDING} />
|
||||||
<Redirect to={ROUTES.NOT_FOUND} />
|
<Redirect to={ROUTES.NOT_FOUND} />
|
||||||
</Switch>
|
</Switch>
|
||||||
|
@ -5,6 +5,7 @@ import PopOver from '../components/common/popover/PopOver';
|
|||||||
import {
|
import {
|
||||||
getDashboardDetailsPath,
|
getDashboardDetailsPath,
|
||||||
getDatasetDetailsPath,
|
getDatasetDetailsPath,
|
||||||
|
getPipelineDetailsPath,
|
||||||
getTopicDetailsPath,
|
getTopicDetailsPath,
|
||||||
} from '../constants/constants';
|
} from '../constants/constants';
|
||||||
import { SearchIndex } from '../enums/search.enum';
|
import { SearchIndex } from '../enums/search.enum';
|
||||||
@ -167,6 +168,9 @@ export const getEntityLink = (
|
|||||||
case SearchIndex.DASHBOARD:
|
case SearchIndex.DASHBOARD:
|
||||||
return getDashboardDetailsPath(fullyQualifiedName);
|
return getDashboardDetailsPath(fullyQualifiedName);
|
||||||
|
|
||||||
|
case SearchIndex.PIPELINE:
|
||||||
|
return getPipelineDetailsPath(fullyQualifiedName);
|
||||||
|
|
||||||
case SearchIndex.TABLE:
|
case SearchIndex.TABLE:
|
||||||
default:
|
default:
|
||||||
return getDatasetDetailsPath(fullyQualifiedName);
|
return getDatasetDetailsPath(fullyQualifiedName);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user