mirror of
https://github.com/datahub-project/datahub.git
synced 2025-12-26 17:37:33 +00:00
feat(health): Adding Entity Health Status to the Lineage Graph View (#8739)
This commit is contained in:
parent
04bf8866c5
commit
4539a1cf20
@ -317,6 +317,7 @@ export class DatasetEntity implements Entity<Dataset> {
|
||||
subtype: entity?.subTypes?.typeNames?.[0] || undefined,
|
||||
icon: entity?.platform?.properties?.logoUrl || undefined,
|
||||
platform: entity?.platform,
|
||||
health: entity?.health || undefined,
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@ import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Health } from '../../../../../../types.generated';
|
||||
import { getHealthSummaryIcon, isUnhealthy } from '../../../../../shared/health/healthUtils';
|
||||
import { getHealthSummaryIcon, HealthSummaryIconType, isUnhealthy } from '../../../../../shared/health/healthUtils';
|
||||
import { EntityHealthPopover } from './EntityHealthPopover';
|
||||
|
||||
const Container = styled.div`
|
||||
@ -14,17 +14,19 @@ const Container = styled.div`
|
||||
type Props = {
|
||||
health: Health[];
|
||||
baseUrl: string;
|
||||
fontSize?: number;
|
||||
tooltipPlacement?: any;
|
||||
};
|
||||
|
||||
export const EntityHealth = ({ health, baseUrl }: Props) => {
|
||||
export const EntityHealth = ({ health, baseUrl, fontSize, tooltipPlacement }: Props) => {
|
||||
const unhealthy = isUnhealthy(health);
|
||||
const icon = getHealthSummaryIcon(health);
|
||||
const icon = getHealthSummaryIcon(health, HealthSummaryIconType.FILLED, fontSize);
|
||||
return (
|
||||
<>
|
||||
{(unhealthy && (
|
||||
<Link to={`${baseUrl}/Validation`}>
|
||||
<Container>
|
||||
<EntityHealthPopover health={health} baseUrl={baseUrl}>
|
||||
<EntityHealthPopover health={health} baseUrl={baseUrl} placement={tooltipPlacement}>
|
||||
{icon}
|
||||
</EntityHealthPopover>
|
||||
</Container>
|
||||
|
||||
@ -50,10 +50,12 @@ type Props = {
|
||||
health: Health[];
|
||||
baseUrl: string;
|
||||
children: React.ReactNode;
|
||||
fontSize?: number;
|
||||
placement?: any;
|
||||
};
|
||||
|
||||
export const EntityHealthPopover = ({ health, baseUrl, children }: Props) => {
|
||||
const icon = getHealthSummaryIcon(health, HealthSummaryIconType.OUTLINED);
|
||||
export const EntityHealthPopover = ({ health, baseUrl, children, fontSize, placement = 'right' }: Props) => {
|
||||
const icon = getHealthSummaryIcon(health, HealthSummaryIconType.OUTLINED, fontSize);
|
||||
const message = getHealthSummaryMessage(health);
|
||||
return (
|
||||
<Popover
|
||||
@ -71,7 +73,7 @@ export const EntityHealthPopover = ({ health, baseUrl, children }: Props) => {
|
||||
</>
|
||||
}
|
||||
color="#262626"
|
||||
placement="right"
|
||||
placement={placement}
|
||||
zIndex={10000000}
|
||||
>
|
||||
{children}
|
||||
|
||||
@ -12,11 +12,12 @@ import { getShortenedTitle, nodeHeightFromTitleLength } from './utils/titleUtils
|
||||
import { LineageExplorerContext } from './utils/LineageExplorerContext';
|
||||
import { useGetEntityLineageLazyQuery } from '../../graphql/lineage.generated';
|
||||
import { useIsSeparateSiblingsMode } from '../entity/shared/siblingUtils';
|
||||
import { centerX, centerY, iconHeight, iconWidth, iconX, iconY, textX, width } from './constants';
|
||||
import { centerX, centerY, iconHeight, iconWidth, iconX, iconY, textX, width, healthX, healthY } from './constants';
|
||||
import LineageEntityColumns from './LineageEntityColumns';
|
||||
import { convertInputFieldsToSchemaFields } from './utils/columnLineageUtils';
|
||||
import ManageLineageMenu from './manage/ManageLineageMenu';
|
||||
import { useGetLineageTimeParams } from './utils/useGetLineageTimeParams';
|
||||
import { EntityHealth } from '../entity/shared/containers/profile/header/EntityHealth';
|
||||
|
||||
const CLICK_DELAY_THRESHOLD = 1000;
|
||||
const DRAG_DISTANCE_THRESHOLD = 20;
|
||||
@ -136,6 +137,11 @@ export default function LineageEntityNode({
|
||||
capitalizeFirstLetterOnly(node.data.subtype) ||
|
||||
(node.data.type && entityRegistry.getEntityName(node.data.type));
|
||||
|
||||
// Health
|
||||
const { health } = node.data;
|
||||
const baseUrl = node.data.type && node.data.urn && entityRegistry.getEntityUrl(node.data.type, node.data.urn);
|
||||
const hasHealth = (health && baseUrl) || false;
|
||||
|
||||
return (
|
||||
<PointerGroup data-testid={`node-${node.data.urn}-${direction}`} top={node.x} left={node.y}>
|
||||
{unexploredHiddenChildren && (isHovered || isSelected) ? (
|
||||
@ -359,6 +365,16 @@ export default function LineageEntityNode({
|
||||
{getShortenedTitle(node.data.name, width)}
|
||||
</UnselectableText>
|
||||
)}
|
||||
<foreignObject x={healthX} y={healthY} width="20" height="20">
|
||||
{hasHealth && (
|
||||
<EntityHealth
|
||||
health={health as any}
|
||||
baseUrl={baseUrl as any}
|
||||
fontSize={20}
|
||||
tooltipPlacement="left"
|
||||
/>
|
||||
)}
|
||||
</foreignObject>
|
||||
</Group>
|
||||
{unexploredHiddenChildren && isHovered ? (
|
||||
<UnselectableText
|
||||
|
||||
@ -20,3 +20,5 @@ export const iconY = -iconHeight / 2;
|
||||
export const centerX = -width / 2;
|
||||
export const centerY = -height / 2;
|
||||
export const textX = iconX + iconWidth + 8;
|
||||
export const healthX = -width / 2 + 14;
|
||||
export const healthY = -iconHeight / 2 - 8;
|
||||
|
||||
@ -19,6 +19,7 @@ import {
|
||||
Entity,
|
||||
LineageRelationship,
|
||||
SiblingProperties,
|
||||
Health,
|
||||
} from '../../types.generated';
|
||||
|
||||
export type EntitySelectParams = {
|
||||
@ -56,6 +57,7 @@ export type FetchedEntity = {
|
||||
schemaMetadata?: SchemaMetadata;
|
||||
inputFields?: InputFields;
|
||||
canEditLineage?: boolean;
|
||||
health?: Health[];
|
||||
};
|
||||
|
||||
export type NodeData = {
|
||||
@ -79,6 +81,7 @@ export type NodeData = {
|
||||
canEditLineage?: boolean;
|
||||
upstreamRelationships?: Array<LineageRelationship>;
|
||||
downstreamRelationships?: Array<LineageRelationship>;
|
||||
health?: Health[];
|
||||
};
|
||||
|
||||
export type VizNode = {
|
||||
|
||||
@ -67,6 +67,7 @@ export default function constructFetchedNode(
|
||||
canEditLineage: fetchedNode.canEditLineage,
|
||||
upstreamRelationships: fetchedNode?.upstreamRelationships || [],
|
||||
downstreamRelationships: fetchedNode?.downstreamRelationships || [],
|
||||
health: fetchedNode?.health,
|
||||
};
|
||||
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
|
||||
@ -100,6 +100,7 @@ export default function constructTree(
|
||||
canEditLineage: fetchedEntity?.canEditLineage,
|
||||
upstreamRelationships: fetchedEntity?.upstreamRelationships || [],
|
||||
downstreamRelationships: fetchedEntity?.downstreamRelationships || [],
|
||||
health: fetchedEntity?.health,
|
||||
};
|
||||
const lineageConfig = entityRegistry.getLineageVizConfig(entityAndType.type, entityAndType.entity);
|
||||
let updatedLineageConfig = { ...lineageConfig };
|
||||
|
||||
@ -11,13 +11,17 @@ import { HealthStatus, HealthStatusType, Health } from '../../../types.generated
|
||||
|
||||
const HEALTH_INDICATOR_COLOR = '#d48806';
|
||||
|
||||
const UnhealthyIconFilled = styled(ExclamationCircleTwoTone)`
|
||||
font-size: 16px;
|
||||
const UnhealthyIconFilled = styled(ExclamationCircleTwoTone)<{ fontSize: number }>`
|
||||
&& {
|
||||
font-size: ${(props) => props.fontSize}px;
|
||||
}
|
||||
`;
|
||||
|
||||
const UnhealthyIconOutlined = styled(ExclamationCircleOutlined)`
|
||||
const UnhealthyIconOutlined = styled(ExclamationCircleOutlined)<{ fontSize: number }>`
|
||||
color: ${HEALTH_INDICATOR_COLOR};
|
||||
font-size: 16px;
|
||||
&& {
|
||||
font-size: ${(props) => props.fontSize}px;
|
||||
}
|
||||
`;
|
||||
|
||||
export enum HealthSummaryIconType {
|
||||
@ -32,12 +36,16 @@ export const isUnhealthy = (healths: Health[]) => {
|
||||
return isFailingAssertions;
|
||||
};
|
||||
|
||||
export const getHealthSummaryIcon = (healths: Health[], type: HealthSummaryIconType = HealthSummaryIconType.FILLED) => {
|
||||
export const getHealthSummaryIcon = (
|
||||
healths: Health[],
|
||||
type: HealthSummaryIconType = HealthSummaryIconType.FILLED,
|
||||
fontSize = 16,
|
||||
) => {
|
||||
const unhealthy = isUnhealthy(healths);
|
||||
return unhealthy
|
||||
? (type === HealthSummaryIconType.FILLED && <UnhealthyIconFilled twoToneColor={HEALTH_INDICATOR_COLOR} />) || (
|
||||
<UnhealthyIconOutlined />
|
||||
)
|
||||
? (type === HealthSummaryIconType.FILLED && (
|
||||
<UnhealthyIconFilled twoToneColor={HEALTH_INDICATOR_COLOR} fontSize={fontSize} />
|
||||
)) || <UnhealthyIconOutlined fontSize={fontSize} />
|
||||
: undefined;
|
||||
};
|
||||
|
||||
|
||||
@ -198,6 +198,12 @@ fragment lineageNodeProperties on EntityWithRelationships {
|
||||
path
|
||||
}
|
||||
}
|
||||
health {
|
||||
type
|
||||
status
|
||||
message
|
||||
causes
|
||||
}
|
||||
}
|
||||
... on MLModelGroup {
|
||||
urn
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user