Adding Lineage section to Dashboard entities (#1864)

* Adding Lineage section to Dashboard entities

* Added dashboard details overview case.
This commit is contained in:
Sachin Chaurasiya 2021-12-22 13:30:31 +05:30 committed by GitHub
parent ae87195371
commit 5db73edc4f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 193 additions and 7 deletions

View File

@ -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}

View File

@ -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;
}

View File

@ -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', () => {

View File

@ -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>

View File

@ -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}

View File

@ -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;

View File

@ -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 [];