mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-12-04 11:33:07 +00:00
Add Design: Entity detail drawer for lineage tab. (#927)
* Add Design: Entity detail drawer for lineage tab. * integrated apis for pipeline and dashboard to show data on drawer. * fixed mainentity should not be clickable. * added support for outside click * addressing review comments * minor tweaks
This commit is contained in:
parent
f15c430023
commit
451b96e15e
@ -0,0 +1,245 @@
|
||||
import { AxiosError, AxiosResponse } from 'axios';
|
||||
import classNames from 'classnames';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { getDatabase } from '../../axiosAPIs/databaseAPI';
|
||||
import { getPipelineByFqn } from '../../axiosAPIs/pipelineAPI';
|
||||
import { getServiceById } from '../../axiosAPIs/serviceAPI';
|
||||
import { getTableDetailsByFQN } from '../../axiosAPIs/tableAPI';
|
||||
import { EntityType } from '../../enums/entity.enum';
|
||||
import { Dashboard } from '../../generated/entity/data/dashboard';
|
||||
import { Pipeline } from '../../generated/entity/data/pipeline';
|
||||
import { Table } from '../../generated/entity/data/table';
|
||||
import { Topic } from '../../generated/entity/data/topic';
|
||||
import useToastContext from '../../hooks/useToastContext';
|
||||
import { getEntityOverview, getEntityTags } from '../../utils/EntityUtils';
|
||||
import { getEntityIcon, getEntityLink } from '../../utils/TableUtils';
|
||||
import { SelectedNode } from '../EntityLineage/EntityLineage.interface';
|
||||
import Loader from '../Loader/Loader';
|
||||
import Tags from '../tags/tags';
|
||||
import { LineageDrawerProps } from './EntityInfoDrawer.interface';
|
||||
import './EntityInfoDrawer.style.css';
|
||||
|
||||
const getHeaderLabel = (
|
||||
v = '',
|
||||
type: string,
|
||||
isMainNode: boolean,
|
||||
separator = '.'
|
||||
) => {
|
||||
const length = v.split(separator).length;
|
||||
|
||||
return (
|
||||
<>
|
||||
{isMainNode ? (
|
||||
<span
|
||||
className="tw-break-words description-text tw-self-center tw-font-normal"
|
||||
data-testid="lineage-entity">
|
||||
{v.split(separator)[length - 1]}
|
||||
</span>
|
||||
) : (
|
||||
<span
|
||||
className="tw-break-words description-text tw-self-center link-text tw-font-normal"
|
||||
data-testid="lineage-entity">
|
||||
<Link to={getEntityLink(type, v)}>
|
||||
{v.split(separator)[length - 1]}
|
||||
</Link>
|
||||
</span>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const EntityInfoDrawer = ({
|
||||
show,
|
||||
onCancel,
|
||||
selectedNode,
|
||||
isMainNode = false,
|
||||
}: LineageDrawerProps) => {
|
||||
const showToast = useToastContext();
|
||||
const [entityDetail, setEntityDetail] = useState<
|
||||
Partial<Table> & Partial<Pipeline> & Partial<Dashboard> & Partial<Topic>
|
||||
>(
|
||||
{} as Partial<Table> &
|
||||
Partial<Pipeline> &
|
||||
Partial<Dashboard> &
|
||||
Partial<Topic>
|
||||
);
|
||||
const [serviceType, setServiceType] = useState<string>('');
|
||||
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
|
||||
const fetchEntityDetail = (selectedNode: SelectedNode) => {
|
||||
switch (selectedNode.type) {
|
||||
case EntityType.TABLE: {
|
||||
setIsLoading(true);
|
||||
getTableDetailsByFQN(selectedNode.name, [
|
||||
'tags',
|
||||
'owner',
|
||||
'columns',
|
||||
'usageSummary',
|
||||
'tableProfile',
|
||||
'database',
|
||||
])
|
||||
.then((res: AxiosResponse) => {
|
||||
getDatabase(res.data.database.id, 'service')
|
||||
.then((resDB: AxiosResponse) => {
|
||||
getServiceById('databaseServices', resDB.data.service?.id).then(
|
||||
(resService: AxiosResponse) => {
|
||||
setServiceType(resService.data.serviceType);
|
||||
}
|
||||
);
|
||||
})
|
||||
.catch((err: AxiosError) => {
|
||||
const msg = err.message;
|
||||
showToast({
|
||||
variant: 'error',
|
||||
body:
|
||||
msg ?? `Error while getting ${selectedNode.name} details`,
|
||||
});
|
||||
});
|
||||
setEntityDetail(res.data);
|
||||
setIsLoading(false);
|
||||
})
|
||||
.catch((err: AxiosError) => {
|
||||
const msg = err.message;
|
||||
showToast({
|
||||
variant: 'error',
|
||||
body: msg ?? `Error while getting ${selectedNode.name} details`,
|
||||
});
|
||||
});
|
||||
|
||||
break;
|
||||
}
|
||||
case EntityType.PIPELINE: {
|
||||
setIsLoading(true);
|
||||
getPipelineByFqn(selectedNode.name, ['tags', 'owner', 'service'])
|
||||
.then((res: AxiosResponse) => {
|
||||
getServiceById('pipelineServices', 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;
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchEntityDetail(selectedNode);
|
||||
}, [selectedNode]);
|
||||
|
||||
return (
|
||||
<div className={classNames('side-drawer', { open: show })}>
|
||||
<header className="tw-flex tw-justify-between">
|
||||
<p className="tw-flex">
|
||||
<span className="tw-mr-2">{getEntityIcon(selectedNode.type)}</span>
|
||||
{getHeaderLabel(selectedNode.name, selectedNode.type, isMainNode)}
|
||||
</p>
|
||||
<div className="tw-flex">
|
||||
<svg
|
||||
className="tw-w-5 tw-h-5 tw-ml-1 tw-cursor-pointer"
|
||||
data-testid="closeDrawer"
|
||||
fill="none"
|
||||
stroke="#6B7280"
|
||||
viewBox="0 0 24 24"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
onClick={() => onCancel(false)}>
|
||||
<path
|
||||
d="M6 18L18 6M6 6l12 12"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth="2"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</header>
|
||||
<hr className="tw-mt-3 tw-border-primary-hover-lite" />
|
||||
{isLoading ? (
|
||||
<Loader />
|
||||
) : (
|
||||
<>
|
||||
<section className="tw-mt-1">
|
||||
<span className="tw-text-grey-muted">Overview</span>
|
||||
<div className="tw-flex tw-flex-col">
|
||||
{getEntityOverview(
|
||||
selectedNode.type,
|
||||
entityDetail,
|
||||
serviceType
|
||||
).map((d) => {
|
||||
return (
|
||||
<p className="tw-py-1.5" key={d.name}>
|
||||
{d.name && <span>{d.name}:</span>}
|
||||
<span
|
||||
className={classNames(
|
||||
{ 'tw-ml-2': d.name },
|
||||
{
|
||||
'link-text': d.isLink,
|
||||
}
|
||||
)}>
|
||||
{d.isLink ? (
|
||||
<Link
|
||||
target={d.isExternal ? '_blank' : '_self'}
|
||||
to={{ pathname: d.url }}>
|
||||
{d.value}
|
||||
</Link>
|
||||
) : (
|
||||
d.value
|
||||
)}
|
||||
</span>
|
||||
</p>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</section>
|
||||
<hr className="tw-mt-3 tw-border-primary-hover-lite" />
|
||||
<section className="tw-mt-1">
|
||||
<span className="tw-text-grey-muted">Tags</span>
|
||||
<div className="tw-flex tw-flex-wrap tw-pt-1.5">
|
||||
{getEntityTags(selectedNode.type, entityDetail).length > 0 ? (
|
||||
getEntityTags(selectedNode.type, entityDetail).map((t) => {
|
||||
return <Tags key={t} tag={`#${t}`} />;
|
||||
})
|
||||
) : (
|
||||
<p className="tw-text-xs tw-text-grey-muted">No Tags added</p>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
<hr className="tw-mt-3 tw-border-primary-hover-lite" />
|
||||
<section className="tw-mt-1">
|
||||
<span className="tw-text-grey-muted">Description</span>
|
||||
<div>
|
||||
{entityDetail.description ?? (
|
||||
<p className="tw-text-xs tw-text-grey-muted">
|
||||
No description added
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default EntityInfoDrawer;
|
||||
@ -0,0 +1,8 @@
|
||||
import { SelectedNode } from '../EntityLineage/EntityLineage.interface';
|
||||
|
||||
export interface LineageDrawerProps {
|
||||
show: boolean;
|
||||
onCancel: (value: boolean) => void;
|
||||
selectedNode: SelectedNode;
|
||||
isMainNode: boolean;
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
.side-drawer {
|
||||
height: 100%;
|
||||
background: white;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 325px;
|
||||
z-index: 200;
|
||||
margin-right: -16px;
|
||||
overflow-y: auto;
|
||||
padding: 16px;
|
||||
transform: translateX(100%);
|
||||
border-left: 1px solid #d9ceee;
|
||||
transition: transform 0.3s ease-out;
|
||||
}
|
||||
|
||||
.backdrop {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(48, 46, 54, 0.6);
|
||||
z-index: 100;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.side-drawer.open {
|
||||
transform: translateX(0);
|
||||
}
|
||||
@ -23,7 +23,9 @@ import {
|
||||
EntityLineage,
|
||||
} from '../../generated/type/entityLineage';
|
||||
import { EntityReference } from '../../generated/type/entityReference';
|
||||
|
||||
import { getEntityIcon } from '../../utils/TableUtils';
|
||||
import EntityInfoDrawer from '../EntityInfoDrawer/EntityInfoDrawer.component';
|
||||
import { SelectedNode } from './EntityLineage.interface';
|
||||
const onLoad = (reactFlowInstance: OnLoadParams) => {
|
||||
reactFlowInstance.fitView();
|
||||
reactFlowInstance.zoomTo(1);
|
||||
@ -49,16 +51,21 @@ const getDataLabel = (v = '', separator = '.') => {
|
||||
const length = v.split(separator).length;
|
||||
|
||||
return (
|
||||
<p className="tw-break-words description-text" data-testid="lineage-entity">
|
||||
<span
|
||||
className="tw-break-words description-text tw-self-center"
|
||||
data-testid="lineage-entity">
|
||||
{v.split(separator)[length - 1]}
|
||||
</p>
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
const positionX = 150;
|
||||
const positionY = 60;
|
||||
|
||||
const getLineageData = (entityLineage: EntityLineage) => {
|
||||
const getLineageData = (
|
||||
entityLineage: EntityLineage,
|
||||
onSelect: (state: boolean, value: SelectedNode) => void
|
||||
) => {
|
||||
const [x, y] = [0, 0];
|
||||
const nodes = entityLineage['nodes'];
|
||||
let upstreamEdges: Array<LineageEdge & { isMapped: boolean }> =
|
||||
@ -96,7 +103,22 @@ const getLineageData = (entityLineage: EntityLineage) => {
|
||||
targetPosition: Position.Left,
|
||||
type: 'default',
|
||||
className: 'leaf-node',
|
||||
data: { label: getDataLabel(node.name as string) },
|
||||
data: {
|
||||
label: (
|
||||
<p
|
||||
className="tw-flex"
|
||||
onClick={() =>
|
||||
onSelect(true, {
|
||||
name: node.name as string,
|
||||
type: node.type,
|
||||
id: `node-${node.id}-${depth}`,
|
||||
})
|
||||
}>
|
||||
<span className="tw-mr-2">{getEntityIcon(node.type)}</span>
|
||||
{getDataLabel(node.name as string)}
|
||||
</p>
|
||||
),
|
||||
},
|
||||
position: {
|
||||
x: -positionX * 2 * depth,
|
||||
y: y + positionY * upDepth,
|
||||
@ -141,7 +163,22 @@ const getLineageData = (entityLineage: EntityLineage) => {
|
||||
targetPosition: Position.Left,
|
||||
type: 'default',
|
||||
className: 'leaf-node',
|
||||
data: { label: getDataLabel(node.name as string) },
|
||||
data: {
|
||||
label: (
|
||||
<span
|
||||
className="tw-flex"
|
||||
onClick={() =>
|
||||
onSelect(true, {
|
||||
name: node.name as string,
|
||||
type: node.type,
|
||||
id: `node-${node.id}-${depth}`,
|
||||
})
|
||||
}>
|
||||
<span className="tw-mr-2">{getEntityIcon(node.type)}</span>
|
||||
{getDataLabel(node.name as string)}
|
||||
</span>
|
||||
),
|
||||
},
|
||||
position: {
|
||||
x: positionX * 2 * depth,
|
||||
y: y + positionY * downDepth,
|
||||
@ -229,7 +266,21 @@ const getLineageData = (entityLineage: EntityLineage) => {
|
||||
: 'output'
|
||||
: 'input',
|
||||
className: 'leaf-node core',
|
||||
data: { label: getDataLabel(mainNode.name as string) },
|
||||
data: {
|
||||
label: (
|
||||
<p
|
||||
className="tw-flex"
|
||||
onClick={() =>
|
||||
onSelect(true, {
|
||||
name: mainNode.name as string,
|
||||
type: mainNode.type,
|
||||
})
|
||||
}>
|
||||
<span className="tw-mr-2">{getEntityIcon(mainNode.type)}</span>
|
||||
{getDataLabel(mainNode.name as string)}
|
||||
</p>
|
||||
),
|
||||
},
|
||||
position: { x: x, y: y },
|
||||
},
|
||||
...UPStreamNodes.map((up) => {
|
||||
@ -257,45 +308,90 @@ const Entitylineage: FunctionComponent<{ entityLineage: EntityLineage }> = ({
|
||||
}: {
|
||||
entityLineage: EntityLineage;
|
||||
}) => {
|
||||
const [elements, setElements] = useState<Elements>(
|
||||
getLineageData(entityLineage) as Elements
|
||||
const [isDrawerOpen, setIsDrawerOpen] = useState<boolean>(false);
|
||||
const [selectedNode, setSelectedNode] = useState<SelectedNode>(
|
||||
{} as SelectedNode
|
||||
);
|
||||
|
||||
const selectNodeHandler = (state: boolean, value: SelectedNode) => {
|
||||
setIsDrawerOpen(state);
|
||||
setSelectedNode(value);
|
||||
};
|
||||
const [elements, setElements] = useState<Elements>(
|
||||
getLineageData(entityLineage, selectNodeHandler) as Elements
|
||||
);
|
||||
|
||||
const closeDrawer = (value: boolean) => {
|
||||
setIsDrawerOpen(value);
|
||||
|
||||
setElements((prevElements) => {
|
||||
return prevElements.map((el) => {
|
||||
if (el.id === selectedNode.id) {
|
||||
return { ...el, className: 'leaf-node' };
|
||||
} else {
|
||||
return el;
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const onElementsRemove = (elementsToRemove: Elements) =>
|
||||
setElements((els) => removeElements(elementsToRemove, els));
|
||||
const onConnect = (params: Edge | Connection) =>
|
||||
setElements((els) => addEdge(params, els));
|
||||
|
||||
const onElementClick = (el: FlowElement) => {
|
||||
setElements((prevElements) => {
|
||||
return prevElements.map((preEl) => {
|
||||
if (preEl.id === el.id) {
|
||||
return { ...preEl, className: `${preEl.className} selected-node` };
|
||||
} else {
|
||||
return { ...preEl, className: 'leaf-node' };
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setElements(getLineageData(entityLineage) as Elements);
|
||||
setElements(getLineageData(entityLineage, selectNodeHandler) as Elements);
|
||||
}, [entityLineage]);
|
||||
|
||||
return (
|
||||
<div className="tw-w-full tw-h-full">
|
||||
{(entityLineage?.downstreamEdges ?? []).length > 0 ||
|
||||
(entityLineage.upstreamEdges ?? []).length ? (
|
||||
<ReactFlowProvider>
|
||||
<ReactFlow
|
||||
panOnScroll
|
||||
elements={elements as Elements}
|
||||
nodesConnectable={false}
|
||||
onConnect={onConnect}
|
||||
onElementsRemove={onElementsRemove}
|
||||
onLoad={onLoad}
|
||||
onNodeContextMenu={onNodeContextMenu}
|
||||
onNodeMouseEnter={onNodeMouseEnter}
|
||||
onNodeMouseLeave={onNodeMouseLeave}
|
||||
onNodeMouseMove={onNodeMouseMove}>
|
||||
<Controls
|
||||
className="tw-top-1 tw-left-1 tw-bottom-full"
|
||||
showInteractive={false}
|
||||
/>
|
||||
</ReactFlow>
|
||||
</ReactFlowProvider>
|
||||
) : (
|
||||
<div className="tw-flex tw-justify-center tw-font-medium tw-items-center tw-border tw-border-main tw-rounded-md tw-p-8">
|
||||
No Lineage data available
|
||||
</div>
|
||||
)}
|
||||
<div className="tw-relative tw-h-full tw--ml-4">
|
||||
<div className="tw-w-full tw-h-full">
|
||||
{(entityLineage?.downstreamEdges ?? []).length > 0 ||
|
||||
(entityLineage.upstreamEdges ?? []).length ? (
|
||||
<ReactFlowProvider>
|
||||
<ReactFlow
|
||||
panOnScroll
|
||||
elements={elements as Elements}
|
||||
nodesConnectable={false}
|
||||
onConnect={onConnect}
|
||||
onElementClick={(_e, el) => onElementClick(el)}
|
||||
onElementsRemove={onElementsRemove}
|
||||
onLoad={onLoad}
|
||||
onNodeContextMenu={onNodeContextMenu}
|
||||
onNodeMouseEnter={onNodeMouseEnter}
|
||||
onNodeMouseLeave={onNodeMouseLeave}
|
||||
onNodeMouseMove={onNodeMouseMove}>
|
||||
<Controls
|
||||
className="tw-top-1 tw-left-1 tw-bottom-full tw-ml-4 tw-mt-4"
|
||||
showInteractive={false}
|
||||
/>
|
||||
</ReactFlow>
|
||||
</ReactFlowProvider>
|
||||
) : (
|
||||
<div className="tw-flex tw-justify-center tw-font-medium tw-items-center tw-border tw-border-main tw-rounded-md tw-p-8">
|
||||
No Lineage data available
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<EntityInfoDrawer
|
||||
isMainNode={selectedNode.name === entityLineage.entity.name}
|
||||
selectedNode={selectedNode}
|
||||
show={isDrawerOpen}
|
||||
onCancel={closeDrawer}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@ -0,0 +1,5 @@
|
||||
export interface SelectedNode {
|
||||
name: string;
|
||||
type: string;
|
||||
id?: string;
|
||||
}
|
||||
@ -308,6 +308,11 @@
|
||||
}
|
||||
.leaf-node.selected,
|
||||
.leaf-node.selected:hover {
|
||||
@apply tw-border-main;
|
||||
box-shadow: 0 0 0 0.5px #e2dce4;
|
||||
}
|
||||
.leaf-node.selected-node,
|
||||
.leaf-node.selected-node:hover {
|
||||
@apply tw-border-primary-active;
|
||||
box-shadow: 0 0 0 0.5px #7147e8;
|
||||
}
|
||||
|
||||
@ -0,0 +1,206 @@
|
||||
import { isNil } from 'lodash';
|
||||
import {
|
||||
getDatabaseDetailsPath,
|
||||
getServiceDetailsPath,
|
||||
getTeamDetailsPath,
|
||||
} from '../constants/constants';
|
||||
import { EntityType } from '../enums/entity.enum';
|
||||
import { Dashboard } from '../generated/entity/data/dashboard';
|
||||
import { Pipeline } from '../generated/entity/data/pipeline';
|
||||
import { Table } from '../generated/entity/data/table';
|
||||
import { Topic } from '../generated/entity/data/topic';
|
||||
import { TagLabel } from '../generated/type/tagLabel';
|
||||
import { getPartialNameFromFQN } from './CommonUtils';
|
||||
import {
|
||||
getOwnerFromId,
|
||||
getTierFromTableTags,
|
||||
getUsagePercentile,
|
||||
} from './TableUtils';
|
||||
import { getTableTags } from './TagsUtils';
|
||||
import { getRelativeDay } from './TimeUtils';
|
||||
|
||||
export const getEntityTags = (
|
||||
type: string,
|
||||
entityDetail: Partial<Table> &
|
||||
Partial<Pipeline> &
|
||||
Partial<Dashboard> &
|
||||
Partial<Topic>
|
||||
): Array<string | undefined> => {
|
||||
switch (type) {
|
||||
case EntityType.TABLE: {
|
||||
const tableTags: Array<TagLabel> = [
|
||||
...getTableTags(entityDetail.columns || []),
|
||||
...(entityDetail.tags || []),
|
||||
];
|
||||
|
||||
return tableTags.map((t) => t.tagFQN);
|
||||
}
|
||||
case EntityType.PIPELINE: {
|
||||
return entityDetail.tags?.map((t) => t.tagFQN) || [];
|
||||
}
|
||||
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export const getEntityOverview = (
|
||||
type: string,
|
||||
entityDetail: Partial<Table> &
|
||||
Partial<Pipeline> &
|
||||
Partial<Dashboard> &
|
||||
Partial<Topic>,
|
||||
serviceType: string
|
||||
): Array<{
|
||||
name: string;
|
||||
value: string;
|
||||
isLink: boolean;
|
||||
isExternal?: boolean;
|
||||
url?: string;
|
||||
}> => {
|
||||
switch (type) {
|
||||
case EntityType.TABLE: {
|
||||
const { fullyQualifiedName, owner, tags, usageSummary, tableProfile } =
|
||||
entityDetail;
|
||||
const [service, database] = getPartialNameFromFQN(
|
||||
fullyQualifiedName ?? '',
|
||||
['service', 'database'],
|
||||
'.'
|
||||
).split('.');
|
||||
const ownerValue = getOwnerFromId(owner?.id);
|
||||
const tier = getTierFromTableTags(tags || []);
|
||||
const usage = !isNil(usageSummary?.weeklyStats?.percentileRank)
|
||||
? getUsagePercentile(usageSummary?.weeklyStats?.percentileRank || 0)
|
||||
: '--';
|
||||
const queries = usageSummary?.weeklyStats?.count.toLocaleString() || '--';
|
||||
const getProfilerRowDiff = (tableProfile: Table['tableProfile']) => {
|
||||
let retDiff;
|
||||
if (tableProfile && tableProfile.length > 0) {
|
||||
let rowDiff: string | number = tableProfile[0].rowCount || 0;
|
||||
const dayDiff = getRelativeDay(
|
||||
tableProfile[0].profileDate
|
||||
? new Date(tableProfile[0].profileDate).getTime()
|
||||
: Date.now()
|
||||
);
|
||||
if (tableProfile.length > 1) {
|
||||
rowDiff = rowDiff - (tableProfile[1].rowCount || 0);
|
||||
}
|
||||
retDiff = `${(rowDiff >= 0 ? '+' : '') + rowDiff} rows ${dayDiff}`;
|
||||
}
|
||||
|
||||
return retDiff;
|
||||
};
|
||||
|
||||
const profilerRowDiff = getProfilerRowDiff(tableProfile);
|
||||
const overview = [
|
||||
{
|
||||
name: 'Service',
|
||||
value: service,
|
||||
url: getServiceDetailsPath(service, serviceType),
|
||||
isLink: true,
|
||||
},
|
||||
{
|
||||
name: 'Database',
|
||||
value: database,
|
||||
url: getDatabaseDetailsPath(
|
||||
getPartialNameFromFQN(
|
||||
fullyQualifiedName ?? '',
|
||||
['service', 'database'],
|
||||
'.'
|
||||
)
|
||||
),
|
||||
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: 'Usage',
|
||||
value: usage,
|
||||
isLink: false,
|
||||
},
|
||||
{
|
||||
name: 'Queries',
|
||||
value: `${queries} past week`,
|
||||
isLink: false,
|
||||
},
|
||||
{
|
||||
name: 'Rows',
|
||||
value:
|
||||
tableProfile && tableProfile[0]?.rowCount
|
||||
? tableProfile[0].rowCount
|
||||
: '--',
|
||||
isLink: false,
|
||||
},
|
||||
{
|
||||
name: 'Columns',
|
||||
value:
|
||||
tableProfile && tableProfile[0]?.columnCount
|
||||
? tableProfile[0].columnCount
|
||||
: '--',
|
||||
isLink: false,
|
||||
},
|
||||
];
|
||||
if (!isNil(profilerRowDiff)) {
|
||||
overview.push({ value: profilerRowDiff, name: '', isLink: false });
|
||||
}
|
||||
|
||||
return overview;
|
||||
}
|
||||
|
||||
case EntityType.PIPELINE: {
|
||||
const { owner, tags, pipelineUrl, 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),
|
||||
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: pipelineUrl as string,
|
||||
isLink: true,
|
||||
isExternal: true,
|
||||
},
|
||||
];
|
||||
|
||||
return overview;
|
||||
}
|
||||
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
};
|
||||
@ -8,6 +8,7 @@ import {
|
||||
getPipelineDetailsPath,
|
||||
getTopicDetailsPath,
|
||||
} from '../constants/constants';
|
||||
import { EntityType } from '../enums/entity.enum';
|
||||
import { SearchIndex } from '../enums/search.enum';
|
||||
import { ConstraintTypes } from '../enums/table.enum';
|
||||
import { Column, Table } from '../generated/entity/data/table';
|
||||
@ -164,15 +165,19 @@ export const getEntityLink = (
|
||||
) => {
|
||||
switch (indexType) {
|
||||
case SearchIndex.TOPIC:
|
||||
case EntityType.TOPIC:
|
||||
return getTopicDetailsPath(fullyQualifiedName);
|
||||
|
||||
case SearchIndex.DASHBOARD:
|
||||
case EntityType.DASHBOARD:
|
||||
return getDashboardDetailsPath(fullyQualifiedName);
|
||||
|
||||
case SearchIndex.PIPELINE:
|
||||
case EntityType.PIPELINE:
|
||||
return getPipelineDetailsPath(fullyQualifiedName);
|
||||
|
||||
case SearchIndex.TABLE:
|
||||
case EntityType.TABLE:
|
||||
default:
|
||||
return getDatasetDetailsPath(fullyQualifiedName);
|
||||
}
|
||||
@ -182,20 +187,24 @@ export const getEntityIcon = (indexType: string) => {
|
||||
let icon = '';
|
||||
switch (indexType) {
|
||||
case SearchIndex.TOPIC:
|
||||
case EntityType.TOPIC:
|
||||
icon = 'topic';
|
||||
|
||||
break;
|
||||
|
||||
case SearchIndex.DASHBOARD:
|
||||
case EntityType.DASHBOARD:
|
||||
icon = 'dashboard';
|
||||
|
||||
break;
|
||||
case SearchIndex.PIPELINE:
|
||||
case EntityType.PIPELINE:
|
||||
icon = 'pipeline';
|
||||
|
||||
break;
|
||||
|
||||
case SearchIndex.TABLE:
|
||||
case EntityType.TABLE:
|
||||
default:
|
||||
icon = 'table';
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user