feat(ui/lineageV2): Show version pill in lineage sidebar and node (#12599)

This commit is contained in:
Andrew Sikowitz 2025-02-27 12:06:34 -08:00 committed by GitHub
parent 6531f0df04
commit a1a20b2f9d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 116 additions and 33 deletions

View File

@ -45,6 +45,7 @@ export function Pill({
onPillClick,
customStyle,
customIconRenderer,
className,
}: PillProps) {
if (!SUPPORTED_CONFIGURATIONS[variant].includes(color)) {
console.debug(`Unsupported configuration for Pill: variant=${variant}, color=${color}`);
@ -62,6 +63,7 @@ export function Pill({
style={{
backgroundColor: customStyle?.backgroundColor,
}}
className={className}
>
{customIconRenderer
? customIconRenderer()

View File

@ -30,6 +30,4 @@ export const PillText = styled.span({
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
fontSize: '12px',
fontWeight: 400,
});

View File

@ -18,6 +18,7 @@ export interface PillProps extends Partial<PillPropsDefaults>, Omit<HTMLAttribut
onClickRightIcon?: (e: React.MouseEvent<HTMLElement, MouseEvent>) => void;
onClickLeftIcon?: (e: React.MouseEvent<HTMLElement, MouseEvent>) => void;
onPillClick?: (e: React.MouseEvent<HTMLElement, MouseEvent>) => void;
className?: string;
}
export type PillStyleProps = PillPropsDefaults & Pick<PillProps, 'color'>;

View File

@ -79,6 +79,7 @@ const getPillFontStyles = (variant: PillVariantOptions, size: SizeOptions): CSSO
md: { fontSize: getFontSize(size), lineHeight: '24px' },
lg: { fontSize: getFontSize(size), lineHeight: '30px' },
xl: { fontSize: getFontSize(size), lineHeight: '34px' },
inherit: { fontSize: 'inherit', lineHeight: 'inherit' },
};
const variantOverrides: Partial<Record<PillVariantOptions, CSSObject>> = {

View File

@ -5,6 +5,7 @@ export enum SizeValues {
md = 'md',
lg = 'lg',
xl = 'xl',
inherit = 'inherit',
}
export type SizeOptions = keyof typeof SizeValues;
export function getSizeName(size: SizeOptions): string {
@ -73,8 +74,9 @@ export enum FontSizeValues {
'2xl' = '2xl',
'3xl' = '3xl',
'4xl' = '4xl',
inherit = 'inherit',
}
export type FontSizeOptions = keyof typeof FontSizeValues;
export type FontSizeOptions = keyof typeof SizeValues | keyof typeof FontSizeValues;
export type FontWeightOptions = 'normal' | 'medium' | 'semiBold' | 'bold';
export type FontColorOptions = MiscColorOptions | ColorOptions;

View File

@ -27,9 +27,8 @@ export const getColor = (color?: MiscColorOptions | ColorOptions, value: number
@param size - the size of the font
*/
export const getFontSize = (size?: FontSizeOptions) => {
let sizeValue = size || '';
if (!size) sizeValue = 'md';
return typography.fontSizes[sizeValue];
if (size === 'inherit') return 'inherit';
return typography.fontSizes[size || 'md'];
};
/*

View File

@ -251,6 +251,7 @@ export default class EntityRegistry {
canEditLineage: genericEntityProperties.privileges?.canEditLineage ?? undefined,
lineageSiblingIcon: genericEntityProperties?.lineageSiblingIcon,
structuredProperties: genericEntityProperties.structuredProperties ?? undefined,
versionProperties: genericEntityProperties.versionProperties ?? undefined,
};
}

View File

@ -1,3 +1,4 @@
import VersioningBadge from '@app/entityV2/shared/versioning/VersioningBadge';
import React from 'react';
import styled from 'styled-components';
import { useEntityData, useRefetch } from '../../../../../entity/shared/EntityContext';
@ -91,6 +92,10 @@ const SidebarEntityHeader = () => {
)}
{entityData?.health && <HealthIcon urn={urn} health={entityData.health} baseUrl={entityUrl} />}
<StructuredPropertyBadge structuredProperties={entityData?.structuredProperties} />
<VersioningBadge
versionProperties={entityData?.versionProperties ?? undefined}
showPopover={false}
/>
</NameWrapper>
<ContextPath
instanceId={entityData?.dataPlatformInstance?.instanceId}

View File

@ -1,6 +1,7 @@
import { VersionPill } from '@app/entityV2/shared/versioning/common';
import VersionsPreview from '@app/entityV2/shared/versioning/VersionsPreview';
import { Popover } from '@components';
import { PillStyleProps } from '@components/components/Pills/types';
import { Space } from 'antd';
import React from 'react';
import { VersionProperties } from '@types';
@ -8,9 +9,15 @@ import { VersionProperties } from '@types';
interface Props {
versionProperties?: VersionProperties;
showPopover: boolean;
className?: string;
}
export default function VersioningBadge({ showPopover, versionProperties }: Props) {
export default function VersioningBadge({
showPopover,
versionProperties,
className,
...props
}: Props & Partial<PillStyleProps>) {
if (!versionProperties?.version.versionTag) {
return null;
}
@ -19,9 +26,11 @@ export default function VersioningBadge({ showPopover, versionProperties }: Prop
<Popover content={showPopover && <VersionsPreview versionSet={versionProperties.versionSet ?? undefined} />}>
<Space>
<VersionPill
{...props}
label={versionProperties?.version.versionTag}
isLatest={versionProperties.isLatest}
clickable
clickable={showPopover}
className={className}
/>
</Space>
</Popover>

View File

@ -1,4 +1,5 @@
import { LoadingOutlined } from '@ant-design/icons';
import VersioningBadge from '@app/entityV2/shared/versioning/VersioningBadge';
import ContainerPath from '@app/lineageV2/LineageEntityNode/ContainerPath';
import GhostEntityMenu from '@app/lineageV2/LineageEntityNode/GhostEntityMenu';
import SchemaFieldNodeContents from '@app/lineageV2/LineageEntityNode/SchemaFieldNodeContents';
@ -222,6 +223,12 @@ const PropertyBadgeWrapper = styled.div`
top: -16px;
`;
const StyledVersioningBadge = styled(VersioningBadge)`
padding: 0 4px;
line-height: 1;
max-width: 100px;
`;
interface Props {
urn: string;
type: EntityType;
@ -441,6 +448,15 @@ function NodeContents(props: Props & LineageEntity & DisplayedColumns) {
title={entity?.name}
highlightText={searchQuery}
highlightColor={highlightColor}
extra={
entity?.versionProperties && (
<StyledVersioningBadge
showPopover={false}
versionProperties={entity.versionProperties}
size="inherit"
/>
)
}
/>
{entity?.deprecation?.deprecated && (
<DeprecationIcon

View File

@ -1,4 +1,5 @@
import useShouldHideTransformations from '@app/lineageV2/useShouldHideTransformations';
import useShouldShowDataProcessInstances from '@app/lineageV2/useShouldShowDataProcessInstances';
import React, { useContext, useEffect, useState } from 'react';
import { ReactFlowProvider } from 'reactflow';
import { EntityType, LineageDirection } from '../../types.generated';
@ -36,7 +37,7 @@ export default function LineageExplorer(props: Props) {
const [columnEdgeVersion, setColumnEdgeVersion] = useState(0);
const [displayVersion, setDisplayVersion] = useState<[number, string[]]>([0, []]);
const [hideTransformations, setHideTransformations] = useShouldHideTransformations();
const [showDataProcessInstances, setShowDataProcessInstances] = useState(false);
const [showDataProcessInstances, setShowDataProcessInstances] = useShouldShowDataProcessInstances();
const [showGhostEntities, setShowGhostEntities] = useState(false);

View File

@ -1,12 +1,12 @@
import { DBT_URN } from '@app/ingest/source/builder/constants';
import { useGetLineageTimeParams } from '@app/lineage/utils/useGetLineageTimeParams';
import { LineageNodesContext } from '@app/lineageV2/common';
import computeOrFilters from '@app/lineageV2/LineageFilterNode/computeOrFilters';
import { DEFAULT_IGNORE_AS_HOPS, DEFAULT_SEARCH_FLAGS } from '@app/lineageV2/useSearchAcrossLineage';
import { DEGREE_FILTER_NAME } from '@app/search/utils/constants';
import { useContext } from 'react';
import { PlatformFieldsFragment } from '../../../graphql/fragments.generated';
import { useAggregateAcrossLineageQuery } from '../../../graphql/search.generated';
import { AggregationMetadata, EntityType, LineageDirection } from '../../../types.generated';
import { AggregationMetadata, LineageDirection } from '../../../types.generated';
import { ENTITY_SUB_TYPE_FILTER_NAME, FILTER_DELIMITER, PLATFORM_FILTER_NAME } from '../../searchV2/utils/constants';
export type PlatformAggregate = readonly [string, number, PlatformFieldsFragment];
@ -25,7 +25,7 @@ export default function useFetchFilterNodeContents(parent: string, direction: Li
const orFilters = computeOrFilters(
[{ field: DEGREE_FILTER_NAME, values: ['1'] }],
hideTransformations,
showDataProcessInstances,
!showDataProcessInstances,
);
const { data } = useAggregateAcrossLineageQuery({
skip,
@ -39,15 +39,10 @@ export default function useFetchFilterNodeContents(parent: string, direction: Li
lineageFlags: {
startTimeMillis,
endTimeMillis,
ignoreAsHops: [
{
entityType: EntityType.Dataset,
platforms: [DBT_URN],
},
{ entityType: EntityType.DataJob },
],
ignoreAsHops: DEFAULT_IGNORE_AS_HOPS,
},
searchFlags: {
...DEFAULT_SEARCH_FLAGS,
skipCache: true, // TODO: Figure how to get around not needing this
},
},

View File

@ -11,6 +11,7 @@ import {
SchemaMetadata,
Status,
StructuredProperties,
VersionProperties,
} from '@types';
export enum LineageAssetType {
@ -67,6 +68,7 @@ export interface FetchedEntityV2 {
inputFields?: InputFields;
canEditLineage?: boolean;
health?: Health[];
versionProperties?: VersionProperties;
lineageAssets?: Map<string, LineageAsset>;
lineageSiblingIcon?: string;
containers?: GenericEntityProperties[];

View File

@ -22,6 +22,24 @@ import {
const PER_HOP_LIMIT = 2;
export const DEFAULT_IGNORE_AS_HOPS = [
{
entityType: EntityType.Dataset,
platforms: [DBT_URN],
},
{
entityType: EntityType.SchemaField,
platforms: [DBT_URN],
},
{ entityType: EntityType.DataJob },
{ entityType: EntityType.DataProcessInstance },
];
export const DEFAULT_SEARCH_FLAGS = {
groupingSpec: { groupingCriteria: [] },
filterNonLatestVersions: false,
};
/**
* Fetches the lineage structure for a given urn and direction, and updates the nodes map with the results.
* @param urn Urn for which to fetch lineage
@ -68,22 +86,11 @@ export default function useSearchAcrossLineage(
startTimeMillis,
endTimeMillis,
entitiesExploredPerHopLimit: PER_HOP_LIMIT,
ignoreAsHops: [
{
entityType: EntityType.Dataset,
platforms: [DBT_URN],
},
{
entityType: EntityType.SchemaField,
platforms: [DBT_URN],
},
{ entityType: EntityType.DataJob },
{ entityType: EntityType.DataProcessInstance },
],
ignoreAsHops: DEFAULT_IGNORE_AS_HOPS,
},
searchFlags: {
...DEFAULT_SEARCH_FLAGS,
skipCache: !!skipCache,
groupingSpec: { groupingCriteria: [] },
},
};

View File

@ -0,0 +1,26 @@
import { useCallback, useState } from 'react';
export default function useShouldShowDataProcessInstances(): [boolean, (value: boolean) => void] {
const defaultValue = inLocalStorage() ? loadFromLocalStorage() : true;
const [showInstances, setShowInstances] = useState(defaultValue);
const setter = useCallback((value: boolean) => {
setShowInstances(value);
saveToLocalStorage(value);
}, []);
return [showInstances, setter];
}
const SHOW_DATA_PROCESS_INSTANCES_KEY = 'lineageV2__showDataProcessInstances';
function inLocalStorage(): boolean {
return localStorage.getItem(SHOW_DATA_PROCESS_INSTANCES_KEY) !== null;
}
function loadFromLocalStorage(): boolean {
return localStorage.getItem(SHOW_DATA_PROCESS_INSTANCES_KEY) === 'true';
}
function saveToLocalStorage(showInstances: boolean) {
localStorage.setItem(SHOW_DATA_PROCESS_INSTANCES_KEY, String(showInstances));
}

View File

@ -23,6 +23,10 @@ const Wrapper = styled.div<{ scale: number; computedRatio: boolean }>`
}
`;
const ExtraWrapper = styled.span`
margin-left: 0.4em;
`;
const MIN_SCALE = 2 / 3;
const TOOLTIP_THRESHOLD = 0.8; // Show tooltip if text is smaller than TOOLTIP_THRESHOLD em
@ -30,11 +34,19 @@ interface Props {
title?: string;
highlightText?: string;
highlightColor?: string;
extra?: React.ReactNode;
className?: string;
placement?: TooltipProps['placement'];
}
export default function OverflowTitle({ title, highlightText, highlightColor, className, placement = 'top' }: Props) {
export default function OverflowTitle({
title,
highlightText,
highlightColor,
extra,
className,
placement = 'top',
}: Props) {
const [scale, setScale] = React.useState<number>(1);
const [ratio, setRatio] = React.useState<number | undefined>(undefined);
@ -59,6 +71,7 @@ export default function OverflowTitle({ title, highlightText, highlightColor, cl
<Wrapper className={className} ref={ref} scale={scale} computedRatio={!!ratio}>
<Highlight search={highlightText} matchStyle={{ backgroundColor: highlightColor }}>
{title}
{!!extra && <ExtraWrapper>{extra}</ExtraWrapper>}
</Highlight>
</Wrapper>
</OptionalTooltip>

View File

@ -694,6 +694,11 @@ fragment entityLineageV2 on Entity {
...lineageV2Result
}
}
... on SupportsVersions {
versionProperties {
...versionProperties
}
}
... on SchemaFieldEntity {
fieldPath
parent {