feat(health): Adding Entity Health Status to the Lineage Graph View (#8739)

This commit is contained in:
John Joyce 2023-08-29 09:32:35 -07:00 committed by GitHub
parent 04bf8866c5
commit 4539a1cf20
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 58 additions and 16 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -198,6 +198,12 @@ fragment lineageNodeProperties on EntityWithRelationships {
path
}
}
health {
type
status
message
causes
}
}
... on MLModelGroup {
urn