feat(ui): Add context paths for Data Products (#14802)

This commit is contained in:
Saketh Varma 2025-09-19 17:09:52 -04:00 committed by GitHub
parent 04b76b5664
commit 01b3ef27d4
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 95 additions and 40 deletions

View File

@ -3,6 +3,7 @@ import React from 'react';
import { GenericEntityProperties } from '@app/entity/shared/types'; import { GenericEntityProperties } from '@app/entity/shared/types';
import { EntityMenuActions, IconStyleType, PreviewType } from '@app/entityV2/Entity'; import { EntityMenuActions, IconStyleType, PreviewType } from '@app/entityV2/Entity';
import { EntityMenuItems } from '@app/entityV2/shared/EntityDropdown/EntityMenuActions'; import { EntityMenuItems } from '@app/entityV2/shared/EntityDropdown/EntityMenuActions';
import { getParentEntities } from '@app/entityV2/shared/containers/profile/header/getParentEntities';
import DefaultPreviewCard from '@app/previewV2/DefaultPreviewCard'; import DefaultPreviewCard from '@app/previewV2/DefaultPreviewCard';
import { useEntityRegistry } from '@app/useEntityRegistry'; import { useEntityRegistry } from '@app/useEntityRegistry';
@ -58,7 +59,7 @@ export const Preview = ({
tags={globalTags || undefined} tags={globalTags || undefined}
owners={owners} owners={owners}
domain={domain} domain={domain}
parentEntities={domain ? [domain] : []} parentEntities={data ? getParentEntities(data, EntityType.DataProduct) : []}
glossaryTerms={glossaryTerms || undefined} glossaryTerms={glossaryTerms || undefined}
entityCount={entityCount} entityCount={entityCount}
externalUrl={externalUrl} externalUrl={externalUrl}

View File

@ -134,7 +134,7 @@ export const DefaultEntityHeader = ({
const displayedEntityType = getDisplayedEntityType(entityData, entityRegistry, entityType); const displayedEntityType = getDisplayedEntityType(entityData, entityRegistry, entityType);
const { platform, platforms } = getEntityPlatforms(entityType, entityData); const { platform, platforms } = getEntityPlatforms(entityType, entityData);
const contextPath = getParentEntities(entityData); const contextPath = getParentEntities(entityData, entityType);
return ( return (
<> <>
<Row> <Row>

View File

@ -2,7 +2,7 @@ import { GenericEntityProperties } from '@app/entity/shared/types';
import { getParentEntities } from '@app/entityV2/shared/containers/profile/header/getParentEntities'; import { getParentEntities } from '@app/entityV2/shared/containers/profile/header/getParentEntities';
import { dataPlatform } from '@src/Mocks'; import { dataPlatform } from '@src/Mocks';
import { EntityType } from '@types'; import { DataProduct, EntityType } from '@types';
const PARENT_CONTAINERS: GenericEntityProperties['parentContainers'] = { const PARENT_CONTAINERS: GenericEntityProperties['parentContainers'] = {
containers: [ containers: [
@ -45,6 +45,20 @@ const PARENT: GenericEntityProperties = {
platform: dataPlatform, platform: dataPlatform,
}; };
const dataProduct: DataProduct = {
urn: 'urn:li:dataProduct:test',
type: EntityType.DataProduct,
domain: {
associatedUrn: '',
domain: {
urn: 'urn:li:domain:bebdad41-c523-469f-9b62-de94f938f603',
id: 'bebdad41-c523-469f-9b62-de94f938f603',
type: EntityType.Domain,
parentDomains: PARENT_DOMAINS,
},
},
};
describe('getContextPath', () => { describe('getContextPath', () => {
it('returns empty array by default', () => { it('returns empty array by default', () => {
const entityData = {}; const entityData = {};
@ -100,4 +114,14 @@ describe('getContextPath', () => {
const contextPath = getParentEntities(entityData); const contextPath = getParentEntities(entityData);
expect(contextPath).toEqual([PARENT]); expect(contextPath).toEqual([PARENT]);
}); });
it('returns correct context path for data products', () => {
const entityData = dataProduct;
const contextPath = getParentEntities(entityData, EntityType.DataProduct);
expect(contextPath).toEqual([
dataProduct.domain?.domain,
...(dataProduct.domain?.domain?.parentDomains?.domains || []),
]);
});
}); });

View File

@ -1,18 +1,40 @@
import { GenericEntityProperties } from '@app/entity/shared/types'; import { GenericEntityProperties } from '@app/entity/shared/types';
import { Entity } from '@types'; import { DataProduct, Entity, EntityType } from '@types';
type GetContextPathInput = Pick< type GetContextPathInput = Pick<
GenericEntityProperties, GenericEntityProperties,
'parent' | 'parentContainers' | 'parentDomains' | 'parentNodes' 'parent' | 'parentContainers' | 'parentDomains' | 'parentNodes' | 'domain'
>; >;
export function getParentEntities(entityData: GetContextPathInput | null): Entity[] { export function getParentEntities(entityData: GetContextPathInput | null, entityType?: EntityType): Entity[] {
if (!entityData) return [];
switch (entityType) {
case EntityType.DataProduct: {
const domain = (entityData as DataProduct).domain?.domain;
return domain ? [domain, ...(domain.parentDomains?.domains || [])] : [];
}
case EntityType.GlossaryTerm:
case EntityType.GlossaryNode:
return entityData.parentNodes?.nodes || [];
case EntityType.Domain:
return entityData.parentDomains?.domains || [];
default: {
// generic fallback
const containerPath = const containerPath =
entityData?.parentContainers?.containers || entityData.parentContainers?.containers ||
entityData?.parentDomains?.domains || entityData.parentDomains?.domains ||
entityData?.parentNodes?.nodes || entityData.parentNodes?.nodes ||
[]; [];
const parentPath: Entity[] = entityData?.parent ? [entityData.parent as Entity] : []; if (containerPath.length) return containerPath;
return containerPath.length ? containerPath : parentPath;
if (entityData.parent) return [entityData.parent as Entity];
return [];
}
}
} }

View File

@ -6,12 +6,13 @@ import React, { useMemo, useRef, useState } from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import analytics, { EntityActionType, EventType } from '@app/analytics'; import analytics, { EntityActionType, EventType } from '@app/analytics';
import { IconStyleType } from '@app/entityV2/Entity'; import { getParentEntities } from '@app/entityV2/shared/containers/profile/header/getParentEntities';
import { handleBatchError } from '@app/entityV2/shared/utils'; import { handleBatchError } from '@app/entityV2/shared/utils';
import { useModulesContext } from '@app/homeV3/module/context/ModulesContext'; import { useModulesContext } from '@app/homeV3/module/context/ModulesContext';
import ContextPath from '@app/previewV2/ContextPath';
import { useEnterKeyListener } from '@app/shared/useEnterKeyListener'; import { useEnterKeyListener } from '@app/shared/useEnterKeyListener';
import { useEntityRegistry } from '@app/useEntityRegistry'; import { useEntityRegistry } from '@app/useEntityRegistry';
import { Button } from '@src/alchemy-components'; import { Button, Text } from '@src/alchemy-components';
import { ANTD_GRAY } from '@src/app/entityV2/shared/constants'; import { ANTD_GRAY } from '@src/app/entityV2/shared/constants';
import { ModalButtonContainer } from '@src/app/shared/button/styledComponents'; import { ModalButtonContainer } from '@src/app/shared/button/styledComponents';
import { useGetRecommendations } from '@src/app/shared/recommendation'; import { useGetRecommendations } from '@src/app/shared/recommendation';
@ -21,14 +22,6 @@ import { useBatchSetDataProductMutation } from '@graphql/dataProduct.generated';
import { useGetAutoCompleteMultipleResultsLazyQuery } from '@graphql/search.generated'; import { useGetAutoCompleteMultipleResultsLazyQuery } from '@graphql/search.generated';
import { DataHubPageModuleType, DataProduct, Entity, EntityType } from '@types'; import { DataHubPageModuleType, DataProduct, Entity, EntityType } from '@types';
const OptionWrapper = styled.div`
padding: 2px 0;
svg {
margin-right: 8px;
}
`;
const LoadingWrapper = styled.div` const LoadingWrapper = styled.div`
display: flex; display: flex;
justify-content: center; justify-content: center;
@ -179,15 +172,23 @@ export default function SetDataProductModal({
value: 'loading', value: 'loading',
}; };
const options = displayedDataProducts.map((result) => ({ const options = displayedDataProducts.map((result) => {
return {
label: ( label: (
<OptionWrapper> <>
{entityRegistry.getIcon(EntityType.DataProduct, 12, IconStyleType.ACCENT, 'black')} <Text size="md">{entityRegistry.getDisplayName(EntityType.DataProduct, result)}</Text>
{entityRegistry.getDisplayName(EntityType.DataProduct, result)} <ContextPath
</OptionWrapper> entityType={EntityType.DataProduct}
displayedEntityType="Data product"
parentEntities={getParentEntities(result as DataProduct, EntityType.DataProduct)}
entityTitleWidth={200}
numVisible={3}
/>
</>
), ),
value: result.urn, value: result.urn,
})); };
});
return ( return (
<Modal <Modal

View File

@ -7,6 +7,7 @@ import EntityTitleLoadingSection from '@app/entityV2/shared/containers/profile/h
import EntityName from '@app/entityV2/shared/containers/profile/header/EntityName'; import EntityName from '@app/entityV2/shared/containers/profile/header/EntityName';
import PlatformHeaderIcons from '@app/entityV2/shared/containers/profile/header/PlatformContent/PlatformHeaderIcons'; import PlatformHeaderIcons from '@app/entityV2/shared/containers/profile/header/PlatformContent/PlatformHeaderIcons';
import StructuredPropertyBadge from '@app/entityV2/shared/containers/profile/header/StructuredPropertyBadge'; import StructuredPropertyBadge from '@app/entityV2/shared/containers/profile/header/StructuredPropertyBadge';
import { getParentEntities } from '@app/entityV2/shared/containers/profile/header/getParentEntities';
import { getDisplayedEntityType } from '@app/entityV2/shared/containers/profile/header/utils'; import { getDisplayedEntityType } from '@app/entityV2/shared/containers/profile/header/utils';
import VersioningBadge from '@app/entityV2/shared/versioning/VersioningBadge'; import VersioningBadge from '@app/entityV2/shared/versioning/VersioningBadge';
import ContextPath from '@app/previewV2/ContextPath'; import ContextPath from '@app/previewV2/ContextPath';
@ -15,7 +16,7 @@ import NotesIcon from '@app/previewV2/NotesIcon';
import HorizontalScroller from '@app/sharedV2/carousel/HorizontalScroller'; import HorizontalScroller from '@app/sharedV2/carousel/HorizontalScroller';
import { useEntityRegistry } from '@app/useEntityRegistry'; import { useEntityRegistry } from '@app/useEntityRegistry';
import { DataPlatform, Entity, EntityType, Post } from '@types'; import { DataPlatform, EntityType, Post } from '@types';
const TitleContainer = styled(HorizontalScroller)` const TitleContainer = styled(HorizontalScroller)`
display: flex; display: flex;
@ -49,13 +50,7 @@ const SidebarEntityHeader = () => {
const platforms = const platforms =
entityType === EntityType.SchemaField ? entityData?.parent?.siblingPlatforms : entityData?.siblingPlatforms; entityType === EntityType.SchemaField ? entityData?.parent?.siblingPlatforms : entityData?.siblingPlatforms;
const containerPath = const parentEntities = getParentEntities(entityData, entityType);
entityData?.parentContainers?.containers ||
entityData?.parentDomains?.domains ||
entityData?.parentNodes?.nodes ||
[];
const parentPath: Entity[] = entityData?.parent ? [entityData.parent as Entity] : [];
const parentEntities = containerPath.length ? containerPath : parentPath;
if (loading) { if (loading) {
return <EntityTitleLoadingSection />; return <EntityTitleLoadingSection />;

View File

@ -228,6 +228,18 @@ fragment autoCompleteFields on Entity {
properties { properties {
name name
} }
domain {
domain {
urn
type
properties {
name
}
parentDomains {
...parentDomainsFields
}
}
}
} }
... on Application { ... on Application {
properties { properties {