feat(ui) Display inputFields in column-lineage visualization (#6303)

This commit is contained in:
Chris Collins 2022-10-28 20:08:13 -04:00 committed by GitHub
parent 5934a0e79f
commit dfc20b3d40
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 135 additions and 14 deletions

View File

@ -24,6 +24,9 @@ public class InputFieldsMapper {
if (field.hasSchemaField()) { if (field.hasSchemaField()) {
fieldResult.setSchemaField(SchemaFieldMapper.map(field.getSchemaField(), entityUrn)); fieldResult.setSchemaField(SchemaFieldMapper.map(field.getSchemaField(), entityUrn));
} }
if (field.hasSchemaFieldUrn()) {
fieldResult.setSchemaFieldUrn(field.getSchemaFieldUrn().toString());
}
return fieldResult; return fieldResult;
}).collect(Collectors.toList())); }).collect(Collectors.toList()));

View File

@ -142,6 +142,7 @@ export default class EntityRegistry {
siblingPlatforms: genericEntityProperties?.siblingPlatforms, siblingPlatforms: genericEntityProperties?.siblingPlatforms,
fineGrainedLineages: genericEntityProperties?.fineGrainedLineages, fineGrainedLineages: genericEntityProperties?.fineGrainedLineages,
schemaMetadata: genericEntityProperties?.schemaMetadata, schemaMetadata: genericEntityProperties?.schemaMetadata,
inputFields: genericEntityProperties?.inputFields,
} as FetchedEntity) || undefined } as FetchedEntity) || undefined
); );
} }

View File

@ -10,7 +10,11 @@ import { centerY, EXPAND_COLLAPSE_COLUMNS_TOGGLE_HEIGHT, iconX, NUM_COLUMNS_PER_
import ColumnNode from './ColumnNode'; import ColumnNode from './ColumnNode';
import NodeColumnsHeader from './NodeColumnsHeader'; import NodeColumnsHeader from './NodeColumnsHeader';
import usePrevious from '../shared/usePrevious'; import usePrevious from '../shared/usePrevious';
import { filterColumns, haveDisplayedFieldsChanged } from './utils/columnLineageUtils'; import {
convertInputFieldsToSchemaFields,
filterColumns,
haveDisplayedFieldsChanged,
} from './utils/columnLineageUtils';
import { useResetPageIndexAfterSelect } from './utils/useResetPageIndexAfterSelect'; import { useResetPageIndexAfterSelect } from './utils/useResetPageIndexAfterSelect';
const StyledPagination = styled(Pagination)` const StyledPagination = styled(Pagination)`
@ -32,7 +36,10 @@ export default function LineageEntityColumns({ node, onHover }: Props) {
const titleHeight = getTitleHeight(expandTitles ? node.data.expandedName || node.data.name : undefined); const titleHeight = getTitleHeight(expandTitles ? node.data.expandedName || node.data.name : undefined);
const fields = columnsByUrn[node.data.urn || ''] || node.data.schemaMetadata?.fields; const fields =
columnsByUrn[node.data.urn || ''] ||
node.data.schemaMetadata?.fields ||
convertInputFieldsToSchemaFields(node.data.inputFields);
const displayedFields = fields?.slice( const displayedFields = fields?.slice(
pageIndex * NUM_COLUMNS_PER_PAGE, pageIndex * NUM_COLUMNS_PER_PAGE,
@ -60,7 +67,8 @@ export default function LineageEntityColumns({ node, onHover }: Props) {
}, [displayedFields, node?.data?.urn, setVisibleColumnsByUrn, previousDisplayedFields]); }, [displayedFields, node?.data?.urn, setVisibleColumnsByUrn, previousDisplayedFields]);
const hasColumnPagination = const hasColumnPagination =
node.data.schemaMetadata?.fields && node.data.schemaMetadata?.fields.length > NUM_COLUMNS_PER_PAGE; (node.data.schemaMetadata?.fields && node.data.schemaMetadata?.fields.length > NUM_COLUMNS_PER_PAGE) ||
(node.data.inputFields?.fields && node.data.inputFields.fields.length > NUM_COLUMNS_PER_PAGE);
return ( return (
<> <>

View File

@ -14,6 +14,7 @@ import { useGetEntityLineageLazyQuery } from '../../graphql/lineage.generated';
import { useIsSeparateSiblingsMode } from '../entity/shared/siblingUtils'; 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 } from './constants';
import LineageEntityColumns from './LineageEntityColumns'; import LineageEntityColumns from './LineageEntityColumns';
import { convertInputFieldsToSchemaFields } from './utils/columnLineageUtils';
const CLICK_DELAY_THRESHOLD = 1000; const CLICK_DELAY_THRESHOLD = 1000;
const DRAG_DISTANCE_THRESHOLD = 20; const DRAG_DISTANCE_THRESHOLD = 20;
@ -100,7 +101,7 @@ export default function LineageEntityNode({
const nodeHeight = nodeHeightFromTitleLength( const nodeHeight = nodeHeightFromTitleLength(
expandTitles ? node.data.expandedName || node.data.name : undefined, expandTitles ? node.data.expandedName || node.data.name : undefined,
node.data.schemaMetadata, node.data.schemaMetadata?.fields || convertInputFieldsToSchemaFields(node.data.inputFields),
showColumns, showColumns,
areColumnsCollapsed, areColumnsCollapsed,
); );
@ -320,7 +321,9 @@ export default function LineageEntityNode({
{unexploredHiddenChildren > 1 ? 'dependencies' : 'dependency'} {unexploredHiddenChildren > 1 ? 'dependencies' : 'dependency'}
</UnselectableText> </UnselectableText>
) : null} ) : null}
{showColumns && node.data.schemaMetadata && <LineageEntityColumns node={node} onHover={onHover} />} {showColumns && (node.data.schemaMetadata || node.data.inputFields) && (
<LineageEntityColumns node={node} onHover={onHover} />
)}
</Group> </Group>
</PointerGroup> </PointerGroup>
); );

View File

@ -15,6 +15,7 @@ import {
DataPlatform, DataPlatform,
FineGrainedLineage, FineGrainedLineage,
SchemaMetadata, SchemaMetadata,
InputFields,
} from '../../types.generated'; } from '../../types.generated';
export type EntitySelectParams = { export type EntitySelectParams = {
@ -47,6 +48,7 @@ export type FetchedEntity = {
siblingPlatforms?: Maybe<DataPlatform[]>; siblingPlatforms?: Maybe<DataPlatform[]>;
fineGrainedLineages?: [FineGrainedLineage]; fineGrainedLineages?: [FineGrainedLineage];
schemaMetadata?: SchemaMetadata; schemaMetadata?: SchemaMetadata;
inputFields?: InputFields;
}; };
export type NodeData = { export type NodeData = {
@ -66,6 +68,7 @@ export type NodeData = {
status?: Maybe<Status>; status?: Maybe<Status>;
siblingPlatforms?: Maybe<DataPlatform[]>; siblingPlatforms?: Maybe<DataPlatform[]>;
schemaMetadata?: SchemaMetadata; schemaMetadata?: SchemaMetadata;
inputFields?: InputFields;
}; };
export type VizNode = { export type VizNode = {

View File

@ -0,0 +1,45 @@
import { getFieldPathFromSchemaFieldUrn, getSourceUrnFromSchemaFieldUrn } from '../columnLineageUtils';
describe('getSourceUrnFromSchemaFieldUrn', () => {
it('should get the source urn for a chart schemaField', () => {
const schemaFieldUrn = 'urn:li:schemaField:(urn:li:chart:(looker,dashboard_elements.1),goal)';
const sourceUrn = getSourceUrnFromSchemaFieldUrn(schemaFieldUrn);
expect(sourceUrn).toBe('urn:li:chart:(looker,dashboard_elements.1)');
});
it('should get the source urn for a dataset schemaField', () => {
const schemaFieldUrn =
'urn:li:schemaField:(urn:li:dataset:(urn:li:dataPlatform:hive,fct_cypress_users_created,PROD),user_name)';
const sourceUrn = getSourceUrnFromSchemaFieldUrn(schemaFieldUrn);
expect(sourceUrn).toBe('urn:li:dataset:(urn:li:dataPlatform:hive,fct_cypress_users_created,PROD)');
});
it('should get the source urn for a nested schemaField', () => {
const schemaFieldUrn =
'urn:li:schemaField:(urn:li:dataset:(urn:li:dataPlatform:hive,fct_cypress_users_created,PROD),user.name.test)';
const sourceUrn = getSourceUrnFromSchemaFieldUrn(schemaFieldUrn);
expect(sourceUrn).toBe('urn:li:dataset:(urn:li:dataPlatform:hive,fct_cypress_users_created,PROD)');
});
});
describe('getFieldPathFromSchemaFieldUrn', () => {
it('should get the fieldPath from a chart schemaField urn', () => {
const schemaFieldUrn = 'urn:li:schemaField:(urn:li:chart:(looker,dashboard_elements.1),goal)';
const sourceUrn = getFieldPathFromSchemaFieldUrn(schemaFieldUrn);
expect(sourceUrn).toBe('goal');
});
it('should get the fieldPath for a dataset schemaField', () => {
const schemaFieldUrn =
'urn:li:schemaField:(urn:li:dataset:(urn:li:dataPlatform:hive,fct_cypress_users_created,PROD),user_name)';
const sourceUrn = getFieldPathFromSchemaFieldUrn(schemaFieldUrn);
expect(sourceUrn).toBe('user_name');
});
it('should get the fieldPath for a nested schemaField', () => {
const schemaFieldUrn =
'urn:li:schemaField:(urn:li:dataset:(urn:li:dataPlatform:hive,fct_cypress_users_created,PROD),user.name.test)';
const sourceUrn = getFieldPathFromSchemaFieldUrn(schemaFieldUrn);
expect(sourceUrn).toBe('user.name.test');
});
});

View File

@ -1,5 +1,5 @@
import { ColumnEdge, FetchedEntity, NodeData } from '../types'; import { ColumnEdge, FetchedEntity, NodeData } from '../types';
import { SchemaField } from '../../../types.generated'; import { InputFields, SchemaField } from '../../../types.generated';
import { downgradeV2FieldPath } from '../../entity/dataset/profile/schema/utils/utils'; import { downgradeV2FieldPath } from '../../entity/dataset/profile/schema/utils/utils';
export function getHighlightedColumnsForNode(highlightedEdges: ColumnEdge[], fields: SchemaField[], nodeUrn: string) { export function getHighlightedColumnsForNode(highlightedEdges: ColumnEdge[], fields: SchemaField[], nodeUrn: string) {
@ -59,6 +59,10 @@ export function sortColumnsByDefault(
}; };
} }
export function convertInputFieldsToSchemaFields(inputFields?: InputFields) {
return inputFields?.fields?.map((field) => field?.schemaField) as SchemaField[] | undefined;
}
export function populateColumnsByUrn( export function populateColumnsByUrn(
columnsByUrn: Record<string, SchemaField[]>, columnsByUrn: Record<string, SchemaField[]>,
fetchedEntities: { [x: string]: FetchedEntity }, fetchedEntities: { [x: string]: FetchedEntity },
@ -71,6 +75,13 @@ export function populateColumnsByUrn(
...populatedColumnsByUrn, ...populatedColumnsByUrn,
[urn]: convertFieldsToV1FieldPath(fetchedEntity.schemaMetadata.fields), [urn]: convertFieldsToV1FieldPath(fetchedEntity.schemaMetadata.fields),
}; };
} else if (fetchedEntity.inputFields?.fields && !columnsByUrn[urn]) {
populatedColumnsByUrn = {
...populatedColumnsByUrn,
[urn]: convertFieldsToV1FieldPath(
convertInputFieldsToSchemaFields(fetchedEntity.inputFields) as SchemaField[],
),
};
} }
}); });
setColumnsByUrn(populatedColumnsByUrn); setColumnsByUrn(populatedColumnsByUrn);
@ -104,3 +115,10 @@ export function filterColumns(
})); }));
} }
} }
export function getSourceUrnFromSchemaFieldUrn(schemaFieldUrn: string) {
return schemaFieldUrn.replace('urn:li:schemaField:(', '').split(')')[0].concat(')');
}
export function getFieldPathFromSchemaFieldUrn(schemaFieldUrn: string) {
return schemaFieldUrn.replace('urn:li:schemaField:(', '').split(')')[1].replace(',', '');
}

View File

@ -63,6 +63,7 @@ export default function constructFetchedNode(
status: fetchedNode.status, status: fetchedNode.status,
siblingPlatforms: fetchedNode.siblingPlatforms, siblingPlatforms: fetchedNode.siblingPlatforms,
schemaMetadata: fetchedNode.schemaMetadata, schemaMetadata: fetchedNode.schemaMetadata,
inputFields: fetchedNode.inputFields,
}; };
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign

View File

@ -24,6 +24,7 @@ export default function constructTree(
unexploredChildren: 0, unexploredChildren: 0,
siblingPlatforms: fetchedEntity?.siblingPlatforms, siblingPlatforms: fetchedEntity?.siblingPlatforms,
schemaMetadata: fetchedEntity?.schemaMetadata, schemaMetadata: fetchedEntity?.schemaMetadata,
inputFields: fetchedEntity?.inputFields,
}; };
const lineageConfig = entityRegistry.getLineageVizConfig(entityAndType.type, entityAndType.entity); const lineageConfig = entityRegistry.getLineageVizConfig(entityAndType.type, entityAndType.entity);
let children: EntityAndType[] = []; let children: EntityAndType[] = [];

View File

@ -1,6 +1,7 @@
import { SchemaFieldRef } from '../../../types.generated'; import { SchemaFieldRef } from '../../../types.generated';
import EntityRegistry from '../../entity/EntityRegistry'; import EntityRegistry from '../../entity/EntityRegistry';
import { EntityAndType, FetchedEntities, FetchedEntity } from '../types'; import { EntityAndType, FetchedEntities, FetchedEntity } from '../types';
import { getFieldPathFromSchemaFieldUrn, getSourceUrnFromSchemaFieldUrn } from './columnLineageUtils';
const breakFieldUrn = (ref: SchemaFieldRef) => { const breakFieldUrn = (ref: SchemaFieldRef) => {
const before = ref.urn; const before = ref.urn;
@ -55,6 +56,22 @@ function extendColumnLineage(lineageVizConfig: FetchedEntity, fineGrainedMap: an
}); });
}); });
} }
if (lineageVizConfig.inputFields?.fields && lineageVizConfig.inputFields.fields.length > 0) {
lineageVizConfig.inputFields.fields.forEach((inputField) => {
if (inputField?.schemaFieldUrn && inputField.schemaField) {
const sourceUrn = getSourceUrnFromSchemaFieldUrn(inputField.schemaFieldUrn);
if (sourceUrn !== lineageVizConfig.urn) {
updateFineGrainedMap(
fineGrainedMap,
sourceUrn,
getFieldPathFromSchemaFieldUrn(inputField.schemaFieldUrn),
lineageVizConfig.urn,
inputField.schemaField.fieldPath,
);
}
}
});
}
} }
export default function extendAsyncEntities( export default function extendAsyncEntities(

View File

@ -9,6 +9,7 @@ import {
width as nodeWidth, width as nodeWidth,
} from '../constants'; } from '../constants';
import { Direction, NodeData, VizEdge, VizNode } from '../types'; import { Direction, NodeData, VizEdge, VizNode } from '../types';
import { convertInputFieldsToSchemaFields } from './columnLineageUtils';
import { getTitleHeight, nodeHeightFromTitleLength } from './titleUtils'; import { getTitleHeight, nodeHeightFromTitleLength } from './titleUtils';
type ProcessArray = { type ProcessArray = {
@ -95,7 +96,7 @@ function layoutNodesForOneDirection(
currentXPosition += currentXPosition +=
nodeHeightFromTitleLength( nodeHeightFromTitleLength(
expandTitles ? node.expandedName || node.name : undefined, expandTitles ? node.expandedName || node.name : undefined,
node.schemaMetadata, node.schemaMetadata?.fields || convertInputFieldsToSchemaFields(node.inputFields),
showColumns, showColumns,
!!collapsedColumnsNodes[node?.urn || 'no-op'], // avoid indexing on undefined if node is undefined !!collapsedColumnsNodes[node?.urn || 'no-op'], // avoid indexing on undefined if node is undefined
) + VERTICAL_SPACE_BETWEEN_NODES; ) + VERTICAL_SPACE_BETWEEN_NODES;

View File

@ -1,4 +1,4 @@
import { SchemaMetadata } from '../../../types.generated'; import { SchemaField } from '../../../types.generated';
import { COLUMN_HEIGHT, EXPAND_COLLAPSE_COLUMNS_TOGGLE_HEIGHT, NUM_COLUMNS_PER_PAGE } from '../constants'; import { COLUMN_HEIGHT, EXPAND_COLLAPSE_COLUMNS_TOGGLE_HEIGHT, NUM_COLUMNS_PER_PAGE } from '../constants';
interface OptionalOptions { interface OptionalOptions {
@ -93,18 +93,17 @@ export function getTitleHeight(title?: string) {
export function nodeHeightFromTitleLength( export function nodeHeightFromTitleLength(
title?: string, title?: string,
schemaMetadata?: SchemaMetadata, fields?: SchemaField[],
showColumns?: boolean, showColumns?: boolean,
collapsed?: boolean, collapsed?: boolean,
) { ) {
let showColumnBuffer = 0; let showColumnBuffer = 0;
let columnPaginationBuffer = 0; let columnPaginationBuffer = 0;
if (showColumns && schemaMetadata) { if (showColumns && fields) {
if (!collapsed) { if (!collapsed) {
showColumnBuffer = showColumnBuffer =
Math.min(schemaMetadata.fields.length, NUM_COLUMNS_PER_PAGE) * COLUMN_HEIGHT + Math.min(fields.length, NUM_COLUMNS_PER_PAGE) * COLUMN_HEIGHT + EXPAND_COLLAPSE_COLUMNS_TOGGLE_HEIGHT;
EXPAND_COLLAPSE_COLUMNS_TOGGLE_HEIGHT; if (fields.length > NUM_COLUMNS_PER_PAGE) {
if (schemaMetadata.fields.length > NUM_COLUMNS_PER_PAGE) {
columnPaginationBuffer = 40; columnPaginationBuffer = 40;
} }
} else { } else {

View File

@ -1,9 +1,11 @@
import { useContext, useEffect } from 'react'; import { useContext, useEffect } from 'react';
import { SchemaField } from '../../../types.generated';
import usePrevious from '../../shared/usePrevious'; import usePrevious from '../../shared/usePrevious';
import { NUM_COLUMNS_PER_PAGE } from '../constants'; import { NUM_COLUMNS_PER_PAGE } from '../constants';
import { FetchedEntity } from '../types'; import { FetchedEntity } from '../types';
import { import {
convertFieldsToV1FieldPath, convertFieldsToV1FieldPath,
convertInputFieldsToSchemaFields,
getHighlightedColumnsForNode, getHighlightedColumnsForNode,
sortColumnsByDefault, sortColumnsByDefault,
sortRelatedLineageColumns, sortRelatedLineageColumns,
@ -19,7 +21,7 @@ export default function useSortColumnsBySelectedField(fetchedEntities: { [x: str
if (selectedField && previousSelectedField !== selectedField) { if (selectedField && previousSelectedField !== selectedField) {
Object.entries(columnsByUrn).forEach(([urn, columns]) => { Object.entries(columnsByUrn).forEach(([urn, columns]) => {
if (selectedField.urn !== urn && columns.length >= NUM_COLUMNS_PER_PAGE) { if (selectedField.urn !== urn && columns.length > NUM_COLUMNS_PER_PAGE) {
const highlightedColumnsForNode = getHighlightedColumnsForNode(highlightedEdges, columns, urn); const highlightedColumnsForNode = getHighlightedColumnsForNode(highlightedEdges, columns, urn);
if (highlightedColumnsForNode.length > 0) { if (highlightedColumnsForNode.length > 0) {
@ -43,6 +45,15 @@ export default function useSortColumnsBySelectedField(fetchedEntities: { [x: str
convertFieldsToV1FieldPath(fetchedEntity.schemaMetadata.fields), convertFieldsToV1FieldPath(fetchedEntity.schemaMetadata.fields),
urn, urn,
); );
} else if (fetchedEntity && fetchedEntity.inputFields) {
updatedColumnsByUrn = sortColumnsByDefault(
updatedColumnsByUrn,
columns,
convertFieldsToV1FieldPath(
convertInputFieldsToSchemaFields(fetchedEntity.inputFields) as SchemaField[],
),
urn,
);
} }
}); });
setColumnsByUrn(updatedColumnsByUrn); setColumnsByUrn(updatedColumnsByUrn);

View File

@ -259,6 +259,11 @@ fragment fullLineageResults on EntityLineageResult {
...schemaMetadataFields ...schemaMetadataFields
} }
} }
... on Chart {
inputFields @include(if: $showColumns) {
...inputFieldsFields
}
}
} }
} }
} }
@ -300,6 +305,11 @@ query getEntityLineage($urn: String!, $separateSiblings: Boolean, $showColumns:
} }
} }
} }
... on Chart {
inputFields @include(if: $showColumns) {
...inputFieldsFields
}
}
... on EntityWithRelationships { ... on EntityWithRelationships {
upstream: lineage( upstream: lineage(
input: { direction: UPSTREAM, start: 0, count: 100, separateSiblings: $separateSiblings } input: { direction: UPSTREAM, start: 0, count: 100, separateSiblings: $separateSiblings }