From 01b3ef27d4d0258c28e6e7ea4a9dfa0f53de340e Mon Sep 17 00:00:00 2001 From: Saketh Varma Date: Fri, 19 Sep 2025 17:09:52 -0400 Subject: [PATCH] feat(ui): Add context paths for Data Products (#14802) --- .../entityV2/dataProduct/preview/Preview.tsx | 3 +- .../profile/header/DefaultEntityHeader.tsx | 2 +- .../profile/header/getContextPath.test.ts | 26 +++++++++++- .../profile/header/getParentEntities.ts | 42 ++++++++++++++----- .../DataProduct/SetDataProductModal.tsx | 39 ++++++++--------- .../profile/sidebar/SidebarEntityHeader.tsx | 11 ++--- datahub-web-react/src/graphql/search.graphql | 12 ++++++ 7 files changed, 95 insertions(+), 40 deletions(-) diff --git a/datahub-web-react/src/app/entityV2/dataProduct/preview/Preview.tsx b/datahub-web-react/src/app/entityV2/dataProduct/preview/Preview.tsx index 3423e4cfdb..ae5c4c96f1 100644 --- a/datahub-web-react/src/app/entityV2/dataProduct/preview/Preview.tsx +++ b/datahub-web-react/src/app/entityV2/dataProduct/preview/Preview.tsx @@ -3,6 +3,7 @@ import React from 'react'; import { GenericEntityProperties } from '@app/entity/shared/types'; import { EntityMenuActions, IconStyleType, PreviewType } from '@app/entityV2/Entity'; 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 { useEntityRegistry } from '@app/useEntityRegistry'; @@ -58,7 +59,7 @@ export const Preview = ({ tags={globalTags || undefined} owners={owners} domain={domain} - parentEntities={domain ? [domain] : []} + parentEntities={data ? getParentEntities(data, EntityType.DataProduct) : []} glossaryTerms={glossaryTerms || undefined} entityCount={entityCount} externalUrl={externalUrl} diff --git a/datahub-web-react/src/app/entityV2/shared/containers/profile/header/DefaultEntityHeader.tsx b/datahub-web-react/src/app/entityV2/shared/containers/profile/header/DefaultEntityHeader.tsx index 0f18347bd0..573b7593ab 100644 --- a/datahub-web-react/src/app/entityV2/shared/containers/profile/header/DefaultEntityHeader.tsx +++ b/datahub-web-react/src/app/entityV2/shared/containers/profile/header/DefaultEntityHeader.tsx @@ -134,7 +134,7 @@ export const DefaultEntityHeader = ({ const displayedEntityType = getDisplayedEntityType(entityData, entityRegistry, entityType); const { platform, platforms } = getEntityPlatforms(entityType, entityData); - const contextPath = getParentEntities(entityData); + const contextPath = getParentEntities(entityData, entityType); return ( <> diff --git a/datahub-web-react/src/app/entityV2/shared/containers/profile/header/getContextPath.test.ts b/datahub-web-react/src/app/entityV2/shared/containers/profile/header/getContextPath.test.ts index 33f7d2b7ac..0bbacfa7e4 100644 --- a/datahub-web-react/src/app/entityV2/shared/containers/profile/header/getContextPath.test.ts +++ b/datahub-web-react/src/app/entityV2/shared/containers/profile/header/getContextPath.test.ts @@ -2,7 +2,7 @@ import { GenericEntityProperties } from '@app/entity/shared/types'; import { getParentEntities } from '@app/entityV2/shared/containers/profile/header/getParentEntities'; import { dataPlatform } from '@src/Mocks'; -import { EntityType } from '@types'; +import { DataProduct, EntityType } from '@types'; const PARENT_CONTAINERS: GenericEntityProperties['parentContainers'] = { containers: [ @@ -45,6 +45,20 @@ const PARENT: GenericEntityProperties = { 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', () => { it('returns empty array by default', () => { const entityData = {}; @@ -100,4 +114,14 @@ describe('getContextPath', () => { const contextPath = getParentEntities(entityData); 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 || []), + ]); + }); }); diff --git a/datahub-web-react/src/app/entityV2/shared/containers/profile/header/getParentEntities.ts b/datahub-web-react/src/app/entityV2/shared/containers/profile/header/getParentEntities.ts index 45e8f73fa0..727baa1978 100644 --- a/datahub-web-react/src/app/entityV2/shared/containers/profile/header/getParentEntities.ts +++ b/datahub-web-react/src/app/entityV2/shared/containers/profile/header/getParentEntities.ts @@ -1,18 +1,40 @@ import { GenericEntityProperties } from '@app/entity/shared/types'; -import { Entity } from '@types'; +import { DataProduct, Entity, EntityType } from '@types'; type GetContextPathInput = Pick< GenericEntityProperties, - 'parent' | 'parentContainers' | 'parentDomains' | 'parentNodes' + 'parent' | 'parentContainers' | 'parentDomains' | 'parentNodes' | 'domain' >; -export function getParentEntities(entityData: GetContextPathInput | null): Entity[] { - const containerPath = - entityData?.parentContainers?.containers || - entityData?.parentDomains?.domains || - entityData?.parentNodes?.nodes || - []; - const parentPath: Entity[] = entityData?.parent ? [entityData.parent as Entity] : []; - return containerPath.length ? containerPath : parentPath; +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 = + entityData.parentContainers?.containers || + entityData.parentDomains?.domains || + entityData.parentNodes?.nodes || + []; + if (containerPath.length) return containerPath; + + if (entityData.parent) return [entityData.parent as Entity]; + + return []; + } + } } diff --git a/datahub-web-react/src/app/entityV2/shared/containers/profile/sidebar/DataProduct/SetDataProductModal.tsx b/datahub-web-react/src/app/entityV2/shared/containers/profile/sidebar/DataProduct/SetDataProductModal.tsx index 846e96e183..de20c46e06 100644 --- a/datahub-web-react/src/app/entityV2/shared/containers/profile/sidebar/DataProduct/SetDataProductModal.tsx +++ b/datahub-web-react/src/app/entityV2/shared/containers/profile/sidebar/DataProduct/SetDataProductModal.tsx @@ -6,12 +6,13 @@ import React, { useMemo, useRef, useState } from 'react'; import styled from 'styled-components'; 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 { useModulesContext } from '@app/homeV3/module/context/ModulesContext'; +import ContextPath from '@app/previewV2/ContextPath'; import { useEnterKeyListener } from '@app/shared/useEnterKeyListener'; 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 { ModalButtonContainer } from '@src/app/shared/button/styledComponents'; import { useGetRecommendations } from '@src/app/shared/recommendation'; @@ -21,14 +22,6 @@ import { useBatchSetDataProductMutation } from '@graphql/dataProduct.generated'; import { useGetAutoCompleteMultipleResultsLazyQuery } from '@graphql/search.generated'; import { DataHubPageModuleType, DataProduct, Entity, EntityType } from '@types'; -const OptionWrapper = styled.div` - padding: 2px 0; - - svg { - margin-right: 8px; - } -`; - const LoadingWrapper = styled.div` display: flex; justify-content: center; @@ -179,15 +172,23 @@ export default function SetDataProductModal({ value: 'loading', }; - const options = displayedDataProducts.map((result) => ({ - label: ( - - {entityRegistry.getIcon(EntityType.DataProduct, 12, IconStyleType.ACCENT, 'black')} - {entityRegistry.getDisplayName(EntityType.DataProduct, result)} - - ), - value: result.urn, - })); + const options = displayedDataProducts.map((result) => { + return { + label: ( + <> + {entityRegistry.getDisplayName(EntityType.DataProduct, result)} + + + ), + value: result.urn, + }; + }); return ( { const platforms = entityType === EntityType.SchemaField ? entityData?.parent?.siblingPlatforms : entityData?.siblingPlatforms; - const containerPath = - entityData?.parentContainers?.containers || - entityData?.parentDomains?.domains || - entityData?.parentNodes?.nodes || - []; - const parentPath: Entity[] = entityData?.parent ? [entityData.parent as Entity] : []; - const parentEntities = containerPath.length ? containerPath : parentPath; + const parentEntities = getParentEntities(entityData, entityType); if (loading) { return ; diff --git a/datahub-web-react/src/graphql/search.graphql b/datahub-web-react/src/graphql/search.graphql index 9c2d7f4781..fb92fe4864 100644 --- a/datahub-web-react/src/graphql/search.graphql +++ b/datahub-web-react/src/graphql/search.graphql @@ -228,6 +228,18 @@ fragment autoCompleteFields on Entity { properties { name } + domain { + domain { + urn + type + properties { + name + } + parentDomains { + ...parentDomainsFields + } + } + } } ... on Application { properties {