mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-08-21 07:28:44 +00:00
Adding Lineage section to Dashboard entities (#1864)
* Adding Lineage section to Dashboard entities * Added dashboard details overview case.
This commit is contained in:
parent
ae87195371
commit
5db73edc4f
@ -35,6 +35,7 @@ import NonAdminAction from '../common/non-admin-action/NonAdminAction';
|
||||
import RichTextEditorPreviewer from '../common/rich-text-editor/RichTextEditorPreviewer';
|
||||
import TabsPane from '../common/TabsPane/TabsPane';
|
||||
import PageContainer from '../containers/PageContainer';
|
||||
import Entitylineage from '../EntityLineage/EntityLineage.component';
|
||||
import ManageTabComponent from '../ManageTab/ManageTab.component';
|
||||
import { ModalWithMarkdownEditor } from '../Modals/ModalWithMarkdownEditor/ModalWithMarkdownEditor';
|
||||
import TagsContainer from '../tags-container/tags-container';
|
||||
@ -64,6 +65,10 @@ const DashboardDetails = ({
|
||||
charts,
|
||||
chartDescriptionUpdateHandler,
|
||||
chartTagUpdateHandler,
|
||||
entityLineage,
|
||||
isNodeLoading,
|
||||
lineageLeafNodes,
|
||||
loadNodeHandler,
|
||||
}: DashboardDetailsProps) => {
|
||||
const { isAuthDisabled } = useAuth();
|
||||
const [isEdit, setIsEdit] = useState(false);
|
||||
@ -102,6 +107,17 @@ const DashboardDetails = ({
|
||||
isProtected: false,
|
||||
position: 1,
|
||||
},
|
||||
{
|
||||
name: 'Lineage',
|
||||
icon: {
|
||||
alt: 'lineage',
|
||||
name: 'icon-lineage',
|
||||
title: 'Lineage',
|
||||
selectedName: 'icon-lineagecolor',
|
||||
},
|
||||
isProtected: false,
|
||||
position: 2,
|
||||
},
|
||||
{
|
||||
name: 'Manage',
|
||||
icon: {
|
||||
@ -112,7 +128,7 @@ const DashboardDetails = ({
|
||||
},
|
||||
isProtected: true,
|
||||
protectedState: !owner || hasEditAccess(),
|
||||
position: 2,
|
||||
position: 3,
|
||||
},
|
||||
];
|
||||
|
||||
@ -464,6 +480,16 @@ const DashboardDetails = ({
|
||||
</>
|
||||
)}
|
||||
{activeTab === 2 && (
|
||||
<div className="tw-h-full">
|
||||
<Entitylineage
|
||||
entityLineage={entityLineage}
|
||||
isNodeLoading={isNodeLoading}
|
||||
lineageLeafNodes={lineageLeafNodes}
|
||||
loadNodeHandler={loadNodeHandler}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{activeTab === 3 && (
|
||||
<div className="tw-mt-4">
|
||||
<ManageTabComponent
|
||||
currentTier={tier?.tagFQN}
|
||||
|
@ -12,10 +12,18 @@
|
||||
*/
|
||||
|
||||
import { Operation } from 'fast-json-patch';
|
||||
import { EntityTags, TableDetail } from 'Models';
|
||||
import {
|
||||
EntityTags,
|
||||
LeafNodes,
|
||||
LineagePos,
|
||||
LoadingNodeState,
|
||||
TableDetail,
|
||||
} from 'Models';
|
||||
import { Chart } from '../../generated/entity/data/chart';
|
||||
import { Dashboard } from '../../generated/entity/data/dashboard';
|
||||
import { User } from '../../generated/entity/teams/user';
|
||||
import { EntityLineage } from '../../generated/type/entityLineage';
|
||||
import { EntityReference } from '../../generated/type/entityReference';
|
||||
import { TagLabel } from '../../generated/type/tagLabel';
|
||||
import { TitleBreadcrumbProps } from '../common/title-breadcrumb/title-breadcrumb.interface';
|
||||
|
||||
@ -24,6 +32,9 @@ export interface ChartType extends Chart {
|
||||
}
|
||||
|
||||
export interface DashboardDetailsProps {
|
||||
isNodeLoading: LoadingNodeState;
|
||||
lineageLeafNodes: LeafNodes;
|
||||
entityLineage: EntityLineage;
|
||||
charts: Array<ChartType>;
|
||||
serviceType: string;
|
||||
dashboardUrl: string;
|
||||
@ -54,4 +65,5 @@ export interface DashboardDetailsProps {
|
||||
patch: Array<Operation>
|
||||
) => void;
|
||||
tagUpdateHandler: (updatedDashboard: Dashboard) => void;
|
||||
loadNodeHandler: (node: EntityReference, pos: LineagePos) => void;
|
||||
}
|
||||
|
@ -12,10 +12,11 @@
|
||||
*/
|
||||
|
||||
import { getAllByTestId, getByTestId, render } from '@testing-library/react';
|
||||
import { TableDetail } from 'Models';
|
||||
import { LeafNodes, LoadingNodeState, TableDetail } from 'Models';
|
||||
import React from 'react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { Dashboard } from '../../generated/entity/data/dashboard';
|
||||
import { EntityLineage } from '../../generated/type/entityLineage';
|
||||
import { TagLabel } from '../../generated/type/tagLabel';
|
||||
import DashboardDetails from './DashboardDetails.component';
|
||||
|
||||
@ -45,6 +46,7 @@ const DashboardDetailsProps = {
|
||||
tagList: [],
|
||||
users: [],
|
||||
dashboardDetails: {} as Dashboard,
|
||||
entityLineage: {} as EntityLineage,
|
||||
entityName: '',
|
||||
activeTab: 1,
|
||||
owner: {} as TableDetail['owner'],
|
||||
@ -61,6 +63,9 @@ const DashboardDetailsProps = {
|
||||
chartDescriptionUpdateHandler: jest.fn(),
|
||||
chartTagUpdateHandler: jest.fn(),
|
||||
tagUpdateHandler: jest.fn(),
|
||||
loadNodeHandler: jest.fn(),
|
||||
lineageLeafNodes: {} as LeafNodes,
|
||||
isNodeLoading: {} as LoadingNodeState,
|
||||
};
|
||||
|
||||
jest.mock('../ManageTab/ManageTab.component', () => {
|
||||
|
@ -15,6 +15,7 @@ import { AxiosError, AxiosResponse } from 'axios';
|
||||
import classNames from 'classnames';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { getDashboardByFqn } from '../../axiosAPIs/dashboardAPI';
|
||||
import { getPipelineByFqn } from '../../axiosAPIs/pipelineAPI';
|
||||
import { getServiceById } from '../../axiosAPIs/serviceAPI';
|
||||
import { getTableDetailsByFQN } from '../../axiosAPIs/tableAPI';
|
||||
@ -135,6 +136,35 @@ const EntityInfoDrawer = ({
|
||||
|
||||
break;
|
||||
}
|
||||
case EntityType.DASHBOARD: {
|
||||
setIsLoading(true);
|
||||
getDashboardByFqn(selectedNode.name, ['tags', 'owner'])
|
||||
.then((res: AxiosResponse) => {
|
||||
getServiceById('dashboardServices', res.data.service?.id)
|
||||
.then((serviceRes: AxiosResponse) => {
|
||||
setServiceType(serviceRes.data.serviceType);
|
||||
})
|
||||
.catch((err: AxiosError) => {
|
||||
const msg = err.message;
|
||||
showToast({
|
||||
variant: 'error',
|
||||
body:
|
||||
msg ?? `Error while getting ${selectedNode.name} service`,
|
||||
});
|
||||
});
|
||||
setEntityDetail(res.data);
|
||||
setIsLoading(false);
|
||||
})
|
||||
.catch((err: AxiosError) => {
|
||||
const msg = err.message;
|
||||
showToast({
|
||||
variant: 'error',
|
||||
body: msg ?? `Error while getting ${selectedNode.name} details`,
|
||||
});
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
@ -224,7 +254,9 @@ const EntityInfoDrawer = ({
|
||||
<section className="tw-mt-1">
|
||||
<span className="tw-text-grey-muted">Description</span>
|
||||
<div>
|
||||
{entityDetail.description ?? (
|
||||
{entityDetail.description?.trim() ? (
|
||||
entityDetail.description
|
||||
) : (
|
||||
<p className="tw-text-xs tw-text-grey-muted">
|
||||
No description added
|
||||
</p>
|
||||
|
@ -13,7 +13,13 @@
|
||||
|
||||
import { AxiosPromise, AxiosResponse } from 'axios';
|
||||
import { compare, Operation } from 'fast-json-patch';
|
||||
import { EntityTags, TableDetail } from 'Models';
|
||||
import {
|
||||
EntityTags,
|
||||
LeafNodes,
|
||||
LineagePos,
|
||||
LoadingNodeState,
|
||||
TableDetail,
|
||||
} from 'Models';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { useHistory, useParams } from 'react-router-dom';
|
||||
import AppState from '../../AppState';
|
||||
@ -24,6 +30,7 @@ import {
|
||||
patchDashboardDetails,
|
||||
removeFollower,
|
||||
} from '../../axiosAPIs/dashboardAPI';
|
||||
import { getLineageByFQN } from '../../axiosAPIs/lineageAPI';
|
||||
import { TitleBreadcrumbProps } from '../../components/common/title-breadcrumb/title-breadcrumb.interface';
|
||||
import DashboardDetails from '../../components/DashboardDetails/DashboardDetails.component';
|
||||
import Loader from '../../components/Loader/Loader';
|
||||
@ -36,12 +43,15 @@ import { ServiceCategory } from '../../enums/service.enum';
|
||||
import { Chart } from '../../generated/entity/data/chart';
|
||||
import { Dashboard } from '../../generated/entity/data/dashboard';
|
||||
import { User } from '../../generated/entity/teams/user';
|
||||
import { EntityLineage } from '../../generated/type/entityLineage';
|
||||
import { EntityReference } from '../../generated/type/entityReference';
|
||||
import { TagLabel } from '../../generated/type/tagLabel';
|
||||
import { addToRecentViewed, getCurrentUserId } from '../../utils/CommonUtils';
|
||||
import {
|
||||
dashboardDetailsTabs,
|
||||
getCurrentDashboardTab,
|
||||
} from '../../utils/DashboardDetailsUtils';
|
||||
import { getEntityLineage } from '../../utils/EntityUtils';
|
||||
import { serviceTypeLogo } from '../../utils/ServiceUtils';
|
||||
import {
|
||||
getOwnerFromId,
|
||||
@ -79,7 +89,15 @@ const DashboardDetailsPage = () => {
|
||||
const [slashedDashboardName, setSlashedDashboardName] = useState<
|
||||
TitleBreadcrumbProps['titleLinks']
|
||||
>([]);
|
||||
|
||||
const [entityLineage, setEntityLineage] = useState<EntityLineage>(
|
||||
{} as EntityLineage
|
||||
);
|
||||
const [isLineageLoading, setIsLineageLoading] = useState<boolean>(true);
|
||||
const [leafNodes, setLeafNodes] = useState<LeafNodes>({} as LeafNodes);
|
||||
const [isNodeLoading, setNodeLoading] = useState<LoadingNodeState>({
|
||||
id: undefined,
|
||||
state: false,
|
||||
});
|
||||
const activeTabHandler = (tabValue: number) => {
|
||||
const currentTabIndex = tabValue - 1;
|
||||
if (dashboardDetailsTabs[currentTabIndex].path !== tab) {
|
||||
@ -142,6 +160,32 @@ const DashboardDetailsPage = () => {
|
||||
return chartsData;
|
||||
};
|
||||
|
||||
const setLeafNode = (val: EntityLineage, pos: LineagePos) => {
|
||||
if (pos === 'to' && val.downstreamEdges?.length === 0) {
|
||||
setLeafNodes((prev) => ({
|
||||
...prev,
|
||||
downStreamNode: [...(prev.downStreamNode ?? []), val.entity.id],
|
||||
}));
|
||||
}
|
||||
if (pos === 'from' && val.upstreamEdges?.length === 0) {
|
||||
setLeafNodes((prev) => ({
|
||||
...prev,
|
||||
upStreamNode: [...(prev.upStreamNode ?? []), val.entity.id],
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
const loadNodeHandler = (node: EntityReference, pos: LineagePos) => {
|
||||
setNodeLoading({ id: node.id, state: true });
|
||||
getLineageByFQN(node.name, node.type).then((res: AxiosResponse) => {
|
||||
setLeafNode(res.data, pos);
|
||||
setEntityLineage(getEntityLineage(entityLineage, res.data, pos));
|
||||
setTimeout(() => {
|
||||
setNodeLoading((prev) => ({ ...prev, state: false }));
|
||||
}, 500);
|
||||
});
|
||||
};
|
||||
|
||||
const fetchDashboardDetail = (dashboardFQN: string) => {
|
||||
setLoading(true);
|
||||
getDashboardByFqn(dashboardFQN, [
|
||||
@ -199,6 +243,14 @@ const DashboardDetailsPage = () => {
|
||||
timestamp: 0,
|
||||
});
|
||||
|
||||
getLineageByFQN(dashboardFQN, EntityType.DASHBOARD)
|
||||
.then((res: AxiosResponse) => {
|
||||
setEntityLineage(res.data);
|
||||
})
|
||||
.finally(() => {
|
||||
setIsLineageLoading(false);
|
||||
});
|
||||
|
||||
setDashboardUrl(dashboardUrl);
|
||||
fetchCharts(charts).then((charts) => setCharts(charts));
|
||||
setLoading(false);
|
||||
@ -296,7 +348,7 @@ const DashboardDetailsPage = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{isLoading ? (
|
||||
{isLoading || isLineageLoading ? (
|
||||
<Loader />
|
||||
) : (
|
||||
<DashboardDetails
|
||||
@ -309,9 +361,13 @@ const DashboardDetailsPage = () => {
|
||||
dashboardUrl={dashboardUrl}
|
||||
description={description}
|
||||
descriptionUpdateHandler={descriptionUpdateHandler}
|
||||
entityLineage={entityLineage}
|
||||
entityName={displayName}
|
||||
followDashboardHandler={followDashboard}
|
||||
followers={followers}
|
||||
isNodeLoading={isNodeLoading}
|
||||
lineageLeafNodes={leafNodes}
|
||||
loadNodeHandler={loadNodeHandler}
|
||||
owner={owner}
|
||||
serviceType={serviceType}
|
||||
setActiveTabHandler={activeTabHandler}
|
||||
|
@ -16,6 +16,10 @@ export const dashboardDetailsTabs = [
|
||||
name: 'Details',
|
||||
path: 'details',
|
||||
},
|
||||
{
|
||||
name: 'Lineage',
|
||||
path: 'lineage',
|
||||
},
|
||||
{
|
||||
name: 'Manage',
|
||||
path: 'manage',
|
||||
@ -26,6 +30,11 @@ export const getCurrentDashboardTab = (tab: string) => {
|
||||
let currentTab = 1;
|
||||
switch (tab) {
|
||||
case 'manage':
|
||||
currentTab = 3;
|
||||
|
||||
break;
|
||||
|
||||
case 'lineage':
|
||||
currentTab = 2;
|
||||
|
||||
break;
|
||||
|
@ -58,6 +58,9 @@ export const getEntityTags = (
|
||||
case EntityType.PIPELINE: {
|
||||
return entityDetail.tags?.map((t) => t.tagFQN) || [];
|
||||
}
|
||||
case EntityType.DASHBOARD: {
|
||||
return entityDetail.tags?.map((t) => t.tagFQN) || [];
|
||||
}
|
||||
|
||||
default:
|
||||
return [];
|
||||
@ -222,6 +225,49 @@ export const getEntityOverview = (
|
||||
|
||||
return overview;
|
||||
}
|
||||
case EntityType.DASHBOARD: {
|
||||
const { owner, tags, dashboardUrl, service, fullyQualifiedName } =
|
||||
entityDetail;
|
||||
const ownerValue = getOwnerFromId(owner?.id);
|
||||
const tier = getTierFromTableTags(tags || []);
|
||||
|
||||
const overview = [
|
||||
{
|
||||
name: 'Service',
|
||||
value: service?.name as string,
|
||||
url: getServiceDetailsPath(
|
||||
service?.name as string,
|
||||
serviceType,
|
||||
ServiceCategory.DASHBOARD_SERVICES
|
||||
),
|
||||
isLink: true,
|
||||
},
|
||||
{
|
||||
name: 'Owner',
|
||||
value: ownerValue?.displayName || ownerValue?.name || '--',
|
||||
url: getTeamDetailsPath(owner?.name || ''),
|
||||
isLink: ownerValue
|
||||
? ownerValue.type === 'team'
|
||||
? true
|
||||
: false
|
||||
: false,
|
||||
},
|
||||
{
|
||||
name: 'Tier',
|
||||
value: tier ? tier.split('.')[1] : '--',
|
||||
isLink: false,
|
||||
},
|
||||
{
|
||||
name: `${serviceType} url`,
|
||||
value: fullyQualifiedName?.split('.')[1] as string,
|
||||
url: dashboardUrl as string,
|
||||
isLink: true,
|
||||
isExternal: true,
|
||||
},
|
||||
];
|
||||
|
||||
return overview;
|
||||
}
|
||||
|
||||
default:
|
||||
return [];
|
||||
|
Loading…
x
Reference in New Issue
Block a user