diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DashboardDetails/DashboardDetails.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DashboardDetails/DashboardDetails.component.tsx index 9a38a751142..a9202201178 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DashboardDetails/DashboardDetails.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DashboardDetails/DashboardDetails.component.tsx @@ -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} /> )}
= ({ deletePostHandler, paging, fetchFeedHandler, - handleExtentionUpdate, + handleExtensionUpdate, updateThreadHandler, entityFieldTaskCount, }: DatasetDetailsProps) => { @@ -834,7 +834,7 @@ const DatasetDetails: React.FC = ({ tableDetails as CustomPropertyProps['entityDetails'] } entityType={EntityType.TABLE} - handleExtentionUpdate={handleExtentionUpdate} + handleExtensionUpdate={handleExtensionUpdate} /> )}
void; - handleExtentionUpdate: (updatedTable: Table) => Promise; + handleExtensionUpdate: (updatedTable: Table) => Promise; updateThreadHandler: ThreadUpdatedFunc; } diff --git a/openmetadata-ui/src/main/resources/ui/src/components/DatasetDetails/DatasetDetails.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/DatasetDetails/DatasetDetails.test.tsx index 72531aa013f..f647cb947c5 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/DatasetDetails/DatasetDetails.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/DatasetDetails/DatasetDetails.test.tsx @@ -174,7 +174,7 @@ const DatasetDetailsProps = { deletePostHandler: jest.fn(), tagUpdateHandler: jest.fn(), fetchFeedHandler: jest.fn(), - handleExtentionUpdate: jest.fn(), + handleExtensionUpdate: jest.fn(), updateThreadHandler: jest.fn(), }; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Execution/TreeView/TreeViewTab.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Execution/TreeView/TreeViewTab.component.tsx index 2e2f97f6ee6..aff6f7f83db 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Execution/TreeView/TreeViewTab.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Execution/TreeView/TreeViewTab.component.tsx @@ -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 = ({ - +
{value.map((status) => (
{status.timestamp}
{status.executionStatus}
- } - key={uniqueId()} - position="bottom"> + }>
))} - +
); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Ingestion/IngestionRecentRun/IngestionRecentRuns.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Ingestion/IngestionRecentRun/IngestionRecentRuns.component.tsx index 412c6c9e17a..f13983477ab 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Ingestion/IngestionRecentRun/IngestionRecentRuns.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Ingestion/IngestionRecentRun/IngestionRecentRuns.component.tsx @@ -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 = ({ classNames, }: Props) => { const { t } = useTranslation(); - const [recentRunStatus, setRecentRunStatus] = useState(); + const [recentRunStatus, setRecentRunStatus] = useState([]); const [loading, setLoading] = useState(true); const fetchPipelineStatus = useCallback(async () => { @@ -59,8 +60,8 @@ export const IngestionRecentRuns: FunctionComponent = ({ 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 = ({ {loading ? ( + ) : isEmpty(recentRunStatus) ? ( +

+ {capitalize(PipelineState.Queued)} +

) : ( - recentRunStatus?.map((r, i) => { + recentRunStatus.map((r, i) => { const status = i === recentRunStatus.length - 1 ? (

= ({ mlModelDetail as CustomPropertyProps['entityDetails'] } entityType={EntityType.MLMODEL} - handleExtentionUpdate={onExtensionUpdate} + handleExtensionUpdate={onExtensionUpdate} /> )}

{ 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( + {} as EntityLineage + ); + const [entityThreadLoading, setEntityThreadLoading] = useState(false); + const [entityThreads, setEntityThreads] = useState([]); + const [entityThreadPaging, setEntityThreadPaging] = useState({ + total: 0, + } as Paging); + + const [feedCount, setFeedCount] = useState(0); + const [entityFieldThreadCount, setEntityFieldThreadCount] = useState< + EntityFieldThreadCount[] + >([]); + const [entityFieldTaskCount, setEntityFieldTaskCount] = useState< + EntityFieldThreadCount[] + >([]); + + const [tagList, setTagList] = useState(); const [threadLink, setThreadLink] = useState(''); @@ -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 = [ { 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 ? : null; + return entityThreadLoading ? : 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) => { + 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) => ( +
handleEditTaskTag(record, index)}> + {deleted ? ( +
+ +
+ ) : ( + { + handleTableTagSelection(); + }} + onSelectionChange={(tags) => { + handleTableTagSelection(tags); + }} + /> + )} +
+ ), + [ + tagList, + editTaskTags, + pipelinePermissions.EditAll, + pipelinePermissions.EditTags, + deleted, + ] + ); + + const taskColumns: ColumnsType = useMemo( + () => [ + { + key: 'name', + dataIndex: 'name', + title: t('label.name'), + render: (name, record) => ( + + {name} + + + ), + }, + { + 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) => ( + +
+ {text ? ( + + ) : ( + No description + )} +
+ {!deleted && ( + + + + )} +
+ ), + }, + { + 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 (
@@ -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} /> -
- -
-
- {activeTab === 1 && ( - <> -
-
- -
-
-
-
- {!isEmpty(tasks) ? ( + + + {t('label.tasks')} + + }> + + + + + + + + {!isEmpty(tasks) ? ( + + +
- ) : ( -
- No task data is available -
- )} -
- - )} - {activeTab === 2 && ( + +
+ + ) : (
-
- -
+ 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"> + {t('label.no-task-available')}
)} - {activeTab === 3 && } - {activeTab === 4 && ( -
- -
- )} - {activeTab === 5 && ( - - )} -
}> - {getLoader()} -
+ + + + {t('label.activity-feed-and-task-plural')}{' '} + {getCountBadge( + feedCount, + '', + PIPELINE_DETAILS_TABS.ActivityFeedsAndTasks === tab + )} + + }> + + +
+
+ +
}> + {getLoader()} +
+
+ + + + + + + {t('label.executions')} + + }> + + + + {t('label.entity-lineage')} + }> +
+
- - +
+ + + {t('label.custom-properties')} + + }> + + + + + + + {editTask && ( )} + {threadLink ? ( ; - pipelineTags: Array; 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; @@ -76,12 +43,5 @@ export interface PipeLineDetailsProp { addLineageHandler: (edge: Edge) => Promise; 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; } diff --git a/openmetadata-ui/src/main/resources/ui/src/components/PipelineDetails/PipelineDetails.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/PipelineDetails/PipelineDetails.test.tsx index 75b0e4bc04a..97031a0807a 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/PipelineDetails/PipelineDetails.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/PipelineDetails/PipelineDetails.test.tsx @@ -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) =>

{count}

), })); -jest.mock('', () => ({ - ExecutionsTab: jest.fn().mockImplementation(() =>

Executions

), -})); +jest.mock('../Execution/Execution.component', () => { + return jest.fn().mockImplementation(() =>

Executions

); +}); 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( , { @@ -251,23 +266,39 @@ describe('Test PipelineDetails component', () => { it('Check if active tab is activity feed', async () => { const { container } = render( - , + , { 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( - , + , { 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( - , + , { 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( - , + , { 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( - , + , { 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(); }); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/TopicDetails/TopicDetails.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/TopicDetails/TopicDetails.component.tsx index 7e241bd45cf..91c7d6d7e00 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/TopicDetails/TopicDetails.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/TopicDetails/TopicDetails.component.tsx @@ -609,7 +609,7 @@ const TopicDetails: React.FC = ({ topicDetails as CustomPropertyProps['entityDetails'] } entityType={EntityType.TOPIC} - handleExtentionUpdate={onExtensionUpdate} + handleExtensionUpdate={onExtensionUpdate} /> )}
Promise; + handleExtensionUpdate: (updatedTable: EntityDetails) => Promise; } diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/CustomPropertyTable/CustomPropertyTable.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/CustomPropertyTable/CustomPropertyTable.test.tsx index b8bf85b58aa..3e953a249b3 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/CustomPropertyTable/CustomPropertyTable.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/CustomPropertyTable/CustomPropertyTable.test.tsx @@ -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, }; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/CustomPropertyTable/CustomPropertyTable.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/CustomPropertyTable/CustomPropertyTable.tsx index d7a33369d3d..d368f10f8a3 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/CustomPropertyTable/CustomPropertyTable.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/CustomPropertyTable/CustomPropertyTable.tsx @@ -25,7 +25,7 @@ import { PropertyValue } from './PropertyValue'; export const CustomPropertyTable: FC = ({ entityDetails, - handleExtentionUpdate, + handleExtensionUpdate, entityType, }) => { const [entityTypeDetail, setEntityTypeDetail] = useState({} as Type); @@ -41,7 +41,7 @@ export const CustomPropertyTable: FC = ({ const onExtensionUpdate = async ( updatedExtension: CustomPropertyProps['entityDetails']['extension'] ) => { - await handleExtentionUpdate({ + await handleExtensionUpdate({ ...entityDetails, extension: updatedExtension, }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/tags-container/tags-container.tsx b/openmetadata-ui/src/main/resources/ui/src/components/tags-container/tags-container.tsx index e6269f9cc35..7085f117fa8 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/tags-container/tags-container.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/tags-container/tags-container.tsx @@ -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 = ({ setTags(updatedTags); }; - const handleSave = (event: React.MouseEvent) => { - event.preventDefault(); - event.stopPropagation(); - onSelectionChange(tags); - }; + const handleSave = useCallback( + (event: React.MouseEvent) => { + event.preventDefault(); + event.stopPropagation(); + onSelectionChange(tags); + }, + [tags] + ); const handleCancel = (event: React.MouseEvent) => { event.preventDefault(); diff --git a/openmetadata-ui/src/main/resources/ui/src/constants/pipeline.constants.ts b/openmetadata-ui/src/main/resources/ui/src/constants/pipeline.constants.ts new file mode 100644 index 00000000000..ebdd6df8f3d --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/constants/pipeline.constants.ts @@ -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', +} diff --git a/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json b/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json index 06739fb64f6..b21f8e442fd 100644 --- a/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json +++ b/openmetadata-ui/src/main/resources/ui/src/locale/languages/en-us.json @@ -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.", diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/DatasetDetailsPage/DatasetDetailsPage.component.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/DatasetDetailsPage/DatasetDetailsPage.component.tsx index 447e67db4ff..d233818f68c 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/DatasetDetailsPage/DatasetDetailsPage.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/DatasetDetailsPage/DatasetDetailsPage.component.tsx @@ -852,7 +852,7 @@ const DatasetDetailsPage: FunctionComponent = () => { fetchFeedHandler={handleFeedFetchFromFeedList} followTableHandler={followTable} followers={followers} - handleExtentionUpdate={handleExtentionUpdate} + handleExtensionUpdate={handleExtentionUpdate} isLineageLoading={isLineageLoading} isNodeLoading={isNodeLoading} isQueriesLoading={isTableQueriesLoading} diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/LineagePage/LineagePage.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/LineagePage/LineagePage.tsx index f1f219bb2e0..ab1bffed1e2 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/LineagePage/LineagePage.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/LineagePage/LineagePage.tsx @@ -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 + ) ); } diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/PipelineDetails/PipelineDetailsPage.component.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/PipelineDetails/PipelineDetailsPage.component.tsx index 527f31a6986..deebc1136be 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/PipelineDetails/PipelineDetailsPage.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/PipelineDetails/PipelineDetailsPage.component.tsx @@ -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; + const { pipelineFQN } = useParams<{ pipelineFQN: string }>(); const [pipelineDetails, setPipelineDetails] = useState( {} as Pipeline ); - const [pipelineId, setPipelineId] = useState(''); + const [isLoading, setLoading] = useState(true); - const [isLineageLoading, setIsLineageLoading] = useState(false); - const [description, setDescription] = useState(''); const [followers, setFollowers] = useState>([]); - const [owner, setOwner] = useState(); - const [tier, setTier] = useState(); - const [tags, setTags] = useState>([]); - const [activeTab, setActiveTab] = useState( - getCurrentPipelineTab(tab) - ); + const [tasks, setTasks] = useState([]); const [pipelineUrl, setPipelineUrl] = useState(''); const [displayName, setDisplayName] = useState(''); - const [serviceType, setServiceType] = useState(''); const [slashedPipelineName, setSlashedPipelineName] = useState< TitleBreadcrumbProps['titleLinks'] >([]); @@ -122,24 +90,9 @@ const PipelineDetailsPage = () => { ); const [leafNodes, setLeafNodes] = useState({} as LeafNodes); - const [currentVersion, setCurrentVersion] = useState(); - const [deleted, setDeleted] = useState(false); const [isError, setIsError] = useState(false); - const [entityThread, setEntityThread] = useState([]); - const [isentityThreadLoading, setIsentityThreadLoading] = - useState(false); - const [feedCount, setFeedCount] = useState(0); - const [entityFieldThreadCount, setEntityFieldThreadCount] = useState< - EntityFieldThreadCount[] - >([]); - const [paging, setPaging] = useState({} as Paging); - - const [pipeLineStatus, setPipelineStatus] = - useState(); - const [entityFieldTaskCount, setEntityFieldTaskCount] = useState< - EntityFieldThreadCount[] - >([]); + const [paging] = useState({} 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((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 ? ( ) : ( {NO_PERMISSION_TO_VIEW} diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/tour-page/TourPage.component.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/tour-page/TourPage.component.tsx index be2d24a5132..eecadc38a10 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/tour-page/TourPage.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/tour-page/TourPage.component.tsx @@ -190,7 +190,7 @@ const TourPage = () => { fetchFeedHandler={handleCountChange} followTableHandler={handleCountChange} followers={mockDatasetData.followers} - handleExtentionUpdate={handleCountChange} + handleExtensionUpdate={handleCountChange} isNodeLoading={{ id: undefined, state: false, diff --git a/openmetadata-ui/src/main/resources/ui/src/styles/app.less b/openmetadata-ui/src/main/resources/ui/src/styles/app.less index 248cf375e62..b0b9f731bb2 100644 --- a/openmetadata-ui/src/main/resources/ui/src/styles/app.less +++ b/openmetadata-ui/src/main/resources/ui/src/styles/app.less @@ -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; } diff --git a/openmetadata-ui/src/main/resources/ui/src/styles/components/tabs.less b/openmetadata-ui/src/main/resources/ui/src/styles/components/tabs.less index 9dc55902384..0c7a93fb292 100644 --- a/openmetadata-ui/src/main/resources/ui/src/styles/components/tabs.less +++ b/openmetadata-ui/src/main/resources/ui/src/styles/components/tabs.less @@ -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%; + } +} diff --git a/openmetadata-ui/src/main/resources/ui/src/styles/spacing.less b/openmetadata-ui/src/main/resources/ui/src/styles/spacing.less index 062e0304954..049b990b66a 100644 --- a/openmetadata-ui/src/main/resources/ui/src/styles/spacing.less +++ b/openmetadata-ui/src/main/resources/ui/src/styles/spacing.less @@ -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; diff --git a/openmetadata-ui/src/main/resources/ui/src/styles/tree.less b/openmetadata-ui/src/main/resources/ui/src/styles/tree.less index 4868fc528a6..655d1ab5cce 100644 --- a/openmetadata-ui/src/main/resources/ui/src/styles/tree.less +++ b/openmetadata-ui/src/main/resources/ui/src/styles/tree.less @@ -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; +} diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/TagsUtils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/TagsUtils.ts index 533ca241658..fada284b9b9 100644 --- a/openmetadata-ui/src/main/resources/ui/src/utils/TagsUtils.ts +++ b/openmetadata-ui/src/main/resources/ui/src/utils/TagsUtils.ts @@ -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) => { try { @@ -118,3 +120,34 @@ export const getTagsWithLabel = (tags: Array) => { 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; +};