mirror of
				https://github.com/open-metadata/OpenMetadata.git
				synced 2025-10-31 10:39:30 +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 | ||||
| ): Promise<AxiosResponse> => { | ||||
|   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 React, { FunctionComponent, useEffect, useState } from 'react'; | ||||
| import { getDashboardByFqn } from '../../axiosAPIs/dashboardAPI'; | ||||
| import { getPipelineByFqn } from '../../axiosAPIs/pipelineAPI'; | ||||
| import { getTableDetailsByFQN } from '../../axiosAPIs/tableAPI'; | ||||
| import { getTopicByFqn } from '../../axiosAPIs/topicsAPI'; | ||||
| import { EntityType } from '../../enums/entity.enum'; | ||||
| @ -121,6 +122,36 @@ const RecentlyViewed: FunctionComponent = () => { | ||||
| 
 | ||||
|           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: | ||||
|           break; | ||||
|       } | ||||
|  | ||||
| @ -36,6 +36,7 @@ export const ERROR404 = 'No data found'; | ||||
| export const ERROR500 = 'Something went wrong'; | ||||
| const PLACEHOLDER_ROUTE_DATASET_FQN = ':datasetFQN'; | ||||
| const PLACEHOLDER_ROUTE_TOPIC_FQN = ':topicFQN'; | ||||
| const PLACEHOLDER_ROUTE_PIPELINE_FQN = ':pipelineFQN'; | ||||
| const PLACEHOLDER_ROUTE_DASHBOARD_FQN = ':dashboardFQN'; | ||||
| const PLACEHOLDER_ROUTE_DATABASE_FQN = ':databaseFQN'; | ||||
| const PLACEHOLDER_ROUTE_SERVICE_FQN = ':serviceFQN'; | ||||
| @ -121,6 +122,7 @@ export const ROUTES = { | ||||
|   TOPIC_DETAILS: `/topic/${PLACEHOLDER_ROUTE_TOPIC_FQN}`, | ||||
|   DASHBOARD_DETAILS: `/dashboard/${PLACEHOLDER_ROUTE_DASHBOARD_FQN}`, | ||||
|   DATABASE_DETAILS: `/database/${PLACEHOLDER_ROUTE_DATABASE_FQN}`, | ||||
|   PIPELINE_DETAILS: `/pipeline/${PLACEHOLDER_ROUTE_PIPELINE_FQN}`, | ||||
|   ONBOARDING: '/onboarding', | ||||
| }; | ||||
| 
 | ||||
| @ -178,6 +180,12 @@ export const getDashboardDetailsPath = (dashboardFQN: string) => { | ||||
| 
 | ||||
|   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']; | ||||
| 
 | ||||
|  | ||||
| @ -19,4 +19,5 @@ export enum EntityType { | ||||
|   DATASET = 'dataset', | ||||
|   TOPIC = 'topic', | ||||
|   DASHBOARD = 'dashboard', | ||||
|   PIPELINE = 'pipeline', | ||||
| } | ||||
|  | ||||
| @ -367,7 +367,7 @@ declare module 'Models' { | ||||
|   // topic interface end
 | ||||
| 
 | ||||
|   interface RecentlyViewedData { | ||||
|     entityType: 'dataset' | 'topic' | 'dashboard'; | ||||
|     entityType: 'dataset' | 'topic' | 'dashboard' | 'pipeline'; | ||||
|     fqn: string; | ||||
|     serviceType?: string; | ||||
|     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: | ||||
|         return getEntityLink(SearchIndex.DASHBOARD, fqn); | ||||
| 
 | ||||
|       case ServiceCategory.PIPELINE_SERVICES: | ||||
|         return getEntityLink(SearchIndex.PIPELINE, fqn); | ||||
| 
 | ||||
|       case ServiceCategory.DATABASE_SERVICES: | ||||
|       default: | ||||
|         return `/database/${fqn}`; | ||||
|  | ||||
| @ -26,6 +26,7 @@ import DatabaseDetails from '../pages/database-details/index'; | ||||
| import ExplorePage from '../pages/explore'; | ||||
| import MyDataPage from '../pages/my-data'; | ||||
| import MyDataDetailsPage from '../pages/my-data-details'; | ||||
| import MyPipelinePage from '../pages/Pipeline-details'; | ||||
| import ReportsPage from '../pages/reports'; | ||||
| import Scorecard from '../pages/scorecard'; | ||||
| import ServicePage from '../pages/service'; | ||||
| @ -67,6 +68,7 @@ const AuthenticatedAppRouter: FunctionComponent = () => { | ||||
|       <Route component={MyDataDetailsPage} path={ROUTES.DATASET_DETAILS} /> | ||||
|       <Route component={MyTopicDetailPage} path={ROUTES.TOPIC_DETAILS} /> | ||||
|       <Route component={MyDashBoardPage} path={ROUTES.DASHBOARD_DETAILS} /> | ||||
|       <Route component={MyPipelinePage} path={ROUTES.PIPELINE_DETAILS} /> | ||||
|       <Route component={Onboarding} path={ROUTES.ONBOARDING} /> | ||||
|       <Redirect to={ROUTES.NOT_FOUND} /> | ||||
|     </Switch> | ||||
|  | ||||
| @ -5,6 +5,7 @@ import PopOver from '../components/common/popover/PopOver'; | ||||
| import { | ||||
|   getDashboardDetailsPath, | ||||
|   getDatasetDetailsPath, | ||||
|   getPipelineDetailsPath, | ||||
|   getTopicDetailsPath, | ||||
| } from '../constants/constants'; | ||||
| import { SearchIndex } from '../enums/search.enum'; | ||||
| @ -167,6 +168,9 @@ export const getEntityLink = ( | ||||
|     case SearchIndex.DASHBOARD: | ||||
|       return getDashboardDetailsPath(fullyQualifiedName); | ||||
| 
 | ||||
|     case SearchIndex.PIPELINE: | ||||
|       return getPipelineDetailsPath(fullyQualifiedName); | ||||
| 
 | ||||
|     case SearchIndex.TABLE: | ||||
|     default: | ||||
|       return getDatasetDetailsPath(fullyQualifiedName); | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 Sachin Chaurasiya
						Sachin Chaurasiya