diff --git a/datahub-web-react/src/app/entity/dataProcessInstance/DataProcessInstanceEntity.tsx b/datahub-web-react/src/app/entity/dataProcessInstance/DataProcessInstanceEntity.tsx index 2396816093..756cc28aa9 100644 --- a/datahub-web-react/src/app/entity/dataProcessInstance/DataProcessInstanceEntity.tsx +++ b/datahub-web-react/src/app/entity/dataProcessInstance/DataProcessInstanceEntity.tsx @@ -183,9 +183,11 @@ export class DataProcessInstanceEntity implements Entity { parentContainers={data.parentContainers} parentEntities={parentEntities} container={data.container || undefined} - duration={firstState?.durationMillis} - status={firstState?.result?.resultType} - startTime={firstState?.timestampMillis} + dataProcessInstanceProps={{ + startTime: firstState?.timestampMillis, + duration: firstState?.durationMillis ?? undefined, + status: firstState?.result?.resultType ?? undefined, + }} /> ); }; diff --git a/datahub-web-react/src/app/entity/dataProcessInstance/preview/Preview.tsx b/datahub-web-react/src/app/entity/dataProcessInstance/preview/Preview.tsx index 9a2acbe11c..ca38fa54e8 100644 --- a/datahub-web-react/src/app/entity/dataProcessInstance/preview/Preview.tsx +++ b/datahub-web-react/src/app/entity/dataProcessInstance/preview/Preview.tsx @@ -39,9 +39,7 @@ export const Preview = ({ health, parentEntities, parentContainers, - duration, - status, - startTime, + dataProcessInstanceProps, }: { urn: string; name: string; @@ -64,9 +62,11 @@ export const Preview = ({ health?: Health[] | null; parentEntities?: Array | null; parentContainers?: ParentContainersResult | null; - duration?: number | null; - status?: string | null; - startTime?: number | null; + dataProcessInstanceProps?: { + startTime?: number; + duration?: number; + status?: string; + }; }): JSX.Element => { const entityRegistry = useEntityRegistry(); return ( @@ -95,9 +95,7 @@ export const Preview = ({ paths={paths} health={health || undefined} parentEntities={parentEntities} - duration={duration} - status={status} - startTime={startTime} + dataProcessInstanceProps={dataProcessInstanceProps} /> ); }; diff --git a/datahub-web-react/src/app/entity/shared/components/styled/search/EmbeddedListSearchSection.tsx b/datahub-web-react/src/app/entity/shared/components/styled/search/EmbeddedListSearchSection.tsx index 9da7b5d0ff..aa897584f2 100644 --- a/datahub-web-react/src/app/entity/shared/components/styled/search/EmbeddedListSearchSection.tsx +++ b/datahub-web-react/src/app/entity/shared/components/styled/search/EmbeddedListSearchSection.tsx @@ -2,13 +2,13 @@ import React from 'react'; import * as QueryString from 'query-string'; import { useHistory, useLocation } from 'react-router'; import { ApolloError } from '@apollo/client'; -import { EntityType, FacetFilterInput } from '../../../../../../types.generated'; +import { FacetFilterInput } from '../../../../../../types.generated'; import useFilters from '../../../../../search/utils/useFilters'; import { navigateToEntitySearchUrl } from './navigateToEntitySearchUrl'; import { FilterSet, GetSearchResultsParams, SearchResultsInterface } from './types'; import { useEntityQueryParams } from '../../../containers/profile/utils'; import { EmbeddedListSearch } from './EmbeddedListSearch'; -import { UnionType } from '../../../../../search/utils/constants'; +import { EMBEDDED_LIST_SEARCH_ENTITY_TYPES, UnionType } from '../../../../../search/utils/constants'; import { DownloadSearchResults, DownloadSearchResultsInput, @@ -16,30 +16,6 @@ import { } from '../../../../../search/utils/types'; const FILTER = 'filter'; -const SEARCH_ENTITY_TYPES = [ - EntityType.Dataset, - EntityType.Dashboard, - EntityType.Chart, - EntityType.Mlmodel, - EntityType.MlmodelGroup, - EntityType.MlfeatureTable, - EntityType.Mlfeature, - EntityType.MlprimaryKey, - EntityType.DataFlow, - EntityType.DataJob, - EntityType.GlossaryTerm, - EntityType.GlossaryNode, - EntityType.Tag, - EntityType.Role, - EntityType.CorpUser, - EntityType.CorpGroup, - EntityType.Container, - EntityType.Domain, - EntityType.DataProduct, - EntityType.Notebook, - EntityType.BusinessAttribute, - EntityType.DataProcessInstance, -]; function getParamsWithoutFilters(params: QueryString.ParsedQuery) { const paramsCopy = { ...params }; @@ -161,7 +137,7 @@ export const EmbeddedListSearchSection = ({ return ( ; notes?: Maybe; versionProperties?: Maybe; + + // Data process instance + lastRunEvent?: Maybe; }; export type GenericEntityUpdate = { diff --git a/datahub-web-react/src/app/entityV2/dataProcessInstance/DataProcessInstanceEntity.tsx b/datahub-web-react/src/app/entityV2/dataProcessInstance/DataProcessInstanceEntity.tsx index 4cd58f7847..43aa426741 100644 --- a/datahub-web-react/src/app/entityV2/dataProcessInstance/DataProcessInstanceEntity.tsx +++ b/datahub-web-react/src/app/entityV2/dataProcessInstance/DataProcessInstanceEntity.tsx @@ -1,3 +1,4 @@ +import DataProcessInstanceSummary from '@src/app/entity/dataProcessInstance/profile/DataProcessInstanceSummary'; import { GenericEntityProperties } from '@app/entity/shared/types'; import { Entity as GraphQLEntity } from '@types'; import { globalEntityRegistryV2 } from '@app/EntityRegistryProvider'; @@ -14,7 +15,6 @@ import { ArrowsClockwise } from 'phosphor-react'; import React from 'react'; import { DataProcessInstance, EntityType, SearchResult } from '../../../types.generated'; import Preview from './preview/Preview'; -import DataProcessInstanceSummary from './profile/DataProcessInstanceSummary'; const getParentEntities = (data: DataProcessInstance): GraphQLEntity[] => { const parentEntity = data?.relationships?.relationships?.find( @@ -79,9 +79,7 @@ export class DataProcessInstanceEntity implements Entity { useEntityQuery={this.useEntityQuery} // useUpdateQuery={useUpdateDataProcessInstanceMutation} getOverrideProperties={this.getOverridePropertiesFromEntity} - headerDropdownItems={ - new Set([EntityMenuItems.UPDATE_DEPRECATION, EntityMenuItems.RAISE_INCIDENT, EntityMenuItems.SHARE]) - } + headerDropdownItems={new Set([EntityMenuItems.SHARE])} tabs={[ { name: 'Summary', @@ -115,6 +113,8 @@ export class DataProcessInstanceEntity implements Entity { (processInstance as GetDataProcessInstanceQuery['dataProcessInstance'])?.optionalPlatform || parent?.platform, parent, + // Not currently rendered in V2 + lastRunEvent: processInstance?.state?.[0], }; }; diff --git a/datahub-web-react/src/app/entityV2/dataProcessInstance/profile/DataProcessInstanceSummary.tsx b/datahub-web-react/src/app/entityV2/dataProcessInstance/profile/DataProcessInstanceSummary.tsx deleted file mode 100644 index d0bfae7b99..0000000000 --- a/datahub-web-react/src/app/entityV2/dataProcessInstance/profile/DataProcessInstanceSummary.tsx +++ /dev/null @@ -1,102 +0,0 @@ -import React from 'react'; -import styled from 'styled-components'; -import { Space, Table, Typography } from 'antd'; -import { formatDetailedDuration } from '@src/app/shared/time/timeUtils'; -import { capitalize } from 'lodash'; -import moment from 'moment'; -import { MlHyperParam, MlMetric, DataProcessInstanceRunResultType } from '../../../../types.generated'; -import { useBaseEntity } from '../../../entity/shared/EntityContext'; -import { InfoItem } from '../../shared/components/styled/InfoItem'; -import { GetDataProcessInstanceQuery } from '../../../../graphql/dataProcessInstance.generated'; -import { Pill } from '../../../../alchemy-components/components/Pills'; - -const TabContent = styled.div` - padding: 16px; -`; - -const InfoItemContainer = styled.div<{ justifyContent }>` - display: flex; - position: relative; - justify-content: ${(props) => props.justifyContent}; - padding: 0px 2px; -`; - -const InfoItemContent = styled.div` - padding-top: 8px; - width: 100px; -`; - -const propertyTableColumns = [ - { - title: 'Name', - dataIndex: 'name', - width: 450, - }, - { - title: 'Value', - dataIndex: 'value', - }, -]; - -export default function DataProcessInstanceSummary() { - const baseEntity = useBaseEntity(); - const dpi = baseEntity?.dataProcessInstance; - - const formatStatus = (state) => { - if (!state || state.length === 0) return '-'; - const result = state[0]?.result?.resultType; - const statusColor = result === DataProcessInstanceRunResultType.Success ? 'green' : 'red'; - return ; - }; - - const formatDuration = (state) => { - if (!state || state.length === 0) return '-'; - return formatDetailedDuration(state[0]?.durationMillis); - }; - - return ( - - - Details - - - - {dpi?.properties?.created?.time - ? moment(dpi.properties.created.time).format('YYYY-MM-DD HH:mm:ss') - : '-'} - - - - {formatStatus(dpi?.state)} - - - {formatDuration(dpi?.state)} - - - {dpi?.mlTrainingRunProperties?.id} - - - {dpi?.properties?.created?.actor} - - - - - {dpi?.mlTrainingRunProperties?.outputUrls} - - - Training Metrics - - Hyper Parameters -
- - - ); -} diff --git a/datahub-web-react/src/app/entityV2/mlModel/MLModelEntity.tsx b/datahub-web-react/src/app/entityV2/mlModel/MLModelEntity.tsx index 93c1afd830..3d4547ce4d 100644 --- a/datahub-web-react/src/app/entityV2/mlModel/MLModelEntity.tsx +++ b/datahub-web-react/src/app/entityV2/mlModel/MLModelEntity.tsx @@ -1,4 +1,5 @@ -import { CodeSandboxOutlined, UnorderedListOutlined } from '@ant-design/icons'; +import { CodeSandboxOutlined, PartitionOutlined, UnorderedListOutlined } from '@ant-design/icons'; +import { LineageTab } from '@app/entityV2/shared/tabs/Lineage/LineageTab'; import * as React from 'react'; import { useGetMlModelQuery } from '../../../graphql/mlModel.generated'; import { EntityType, MlModel, SearchResult } from '../../../types.generated'; @@ -80,6 +81,8 @@ export class MLModelEntity implements Entity { getOverridePropertiesFromEntity = (mlModel?: MlModel | null): GenericEntityProperties => { return { + // eslint-disable-next-line @typescript-eslint/dot-notation + name: mlModel && this.displayName(mlModel), externalUrl: mlModel?.properties?.externalUrl, }; }; @@ -103,6 +106,11 @@ export class MLModelEntity implements Entity { name: 'Documentation', component: DocumentationTab, }, + { + name: 'Lineage', + component: LineageTab, + icon: PartitionOutlined, + }, { name: 'Properties', component: PropertiesTab, @@ -193,8 +201,7 @@ export class MLModelEntity implements Entity { getLineageVizConfig = (entity: MlModel) => { return { urn: entity.urn, - // eslint-disable-next-line @typescript-eslint/dot-notation - name: entity.properties?.['propertiesName'] || entity.name, + name: entity && this.displayName(entity), type: EntityType.Mlmodel, icon: entity.platform?.properties?.logoUrl || undefined, platform: entity.platform, @@ -203,11 +210,16 @@ export class MLModelEntity implements Entity { }; displayName = (data: MlModel) => { - return data.properties?.name || data.name || data.urn; + // eslint-disable-next-line @typescript-eslint/dot-notation + return data.properties?.['propertiesName'] || data.properties?.name || data.name || data.urn; }; getGenericEntityProperties = (mlModel: MlModel) => { - return getDataForEntityType({ data: mlModel, entityType: this.type, getOverrideProperties: (data) => data }); + return getDataForEntityType({ + data: mlModel, + entityType: this.type, + getOverrideProperties: this.getOverridePropertiesFromEntity, + }); }; supportedCapabilities = () => { diff --git a/datahub-web-react/src/app/entityV2/mlModel/preview/Preview.tsx b/datahub-web-react/src/app/entityV2/mlModel/preview/Preview.tsx index 24e635f766..801de4f709 100644 --- a/datahub-web-react/src/app/entityV2/mlModel/preview/Preview.tsx +++ b/datahub-web-react/src/app/entityV2/mlModel/preview/Preview.tsx @@ -31,8 +31,7 @@ export const Preview = ({ return ( ` + display: flex; + position: relative; + justify-content: ${(props) => props.justifyContent}; + padding: 0px 2px; +`; + +const InfoItemContent = styled.div` + padding-top: 8px; + width: 100px; + display: flex; + flex-wrap: wrap; + gap: 5px; +`; + +const JobLink = styled(Link)` + color: ${colors.blue[700]}; + &:hover { + text-decoration: underline; + } +`; + export default function MLModelSummary() { const baseEntity = useBaseEntity(); const model = baseEntity?.mlModel; + const entityRegistry = useEntityRegistry(); const propertyTableColumns = [ { @@ -26,9 +55,72 @@ export default function MLModelSummary() { }, ]; + const renderTrainingJobs = () => { + const trainingJobs = + model?.trainedBy?.relationships?.map((relationship) => relationship.entity).filter(notEmpty) || []; + + if (trainingJobs.length === 0) return '-'; + + return ( +
+ {trainingJobs.map((job, index) => { + const { urn, name } = job as { urn: string; name?: string }; + return ( + + + {name || urn} + + {index < trainingJobs.length - 1 && ', '} + + ); + })} +
+ ); + }; + return ( + Model Details + + + {model?.versionProperties?.version?.versionTag} + + + + {model?.properties?.created?.time + ? moment(model.properties.created.time).format('YYYY-MM-DD HH:mm:ss') + : '-'} + + + + + {model?.properties?.lastModified?.time + ? moment(model.properties.lastModified.time).format('YYYY-MM-DD HH:mm:ss') + : '-'} + + + + {model?.properties?.created?.actor} + + + + + + {model?.versionProperties?.aliases?.map((alias) => ( + + ))} + + + + {renderTrainingJobs()} + + Training Metrics
{ getCollectionName = () => 'ML Groups'; - getOverridePropertiesFromEntity = (_?: MlModelGroup | null): GenericEntityProperties => { - return {}; + getOverridePropertiesFromEntity = (mlModelGroup?: MlModelGroup | null): GenericEntityProperties => { + return { + name: mlModelGroup && this.displayName(mlModelGroup), + }; }; useEntityQuery = useGetMlModelGroupQuery; @@ -93,6 +100,11 @@ export class MLModelGroupEntity implements Entity { name: 'Documentation', component: DocumentationTab, }, + { + name: 'Lineage', + component: LineageTab, + icon: PartitionOutlined, + }, { name: 'Properties', component: PropertiesTab, @@ -175,8 +187,7 @@ export class MLModelGroupEntity implements Entity { getLineageVizConfig = (entity: MlModelGroup) => { return { urn: entity.urn, - // eslint-disable-next-line @typescript-eslint/dot-notation - name: entity.properties?.['propertiesName'] || entity.name, + name: entity && this.displayName(entity), type: EntityType.MlmodelGroup, icon: entity.platform?.properties?.logoUrl || undefined, platform: entity.platform, @@ -185,14 +196,15 @@ export class MLModelGroupEntity implements Entity { }; displayName = (data: MlModelGroup) => { - return data.properties?.name || data.name || data.urn; + // eslint-disable-next-line @typescript-eslint/dot-notation + return data.properties?.['propertiesName'] || data.properties?.name || data.name || data.urn; }; getGenericEntityProperties = (mlModelGroup: MlModelGroup) => { return getDataForEntityType({ data: mlModelGroup, entityType: this.type, - getOverrideProperties: (data) => data, + getOverrideProperties: this.getOverridePropertiesFromEntity, }); }; diff --git a/datahub-web-react/src/app/entityV2/mlModelGroup/preview/Preview.tsx b/datahub-web-react/src/app/entityV2/mlModelGroup/preview/Preview.tsx index 0451cea9ce..5d24f4749c 100644 --- a/datahub-web-react/src/app/entityV2/mlModelGroup/preview/Preview.tsx +++ b/datahub-web-react/src/app/entityV2/mlModelGroup/preview/Preview.tsx @@ -30,8 +30,7 @@ export const Preview = ({ return ( ` + display: flex; + position: relative; + justify-content: ${(props) => props.justifyContent}; + padding: 12px 2px 20px 2px; +`; + +const InfoItemContent = styled.div` + padding-top: 8px; + width: 100px; +`; + +const NameContainer = styled.div` + display: flex; + align-items: center; +`; + +const NameLink = styled.a` + font-weight: 700; + color: inherit; + font-size: 0.9rem; + + &:hover { + color: ${colors.blue[400]} !important; + } +`; + +const TagContainer = styled.div` + display: inline-flex; + margin-left: 0px; + margin-top: 3px; + flex-wrap: wrap; + margin-right: 8px; + backgroundcolor: white; + gap: 5px; +`; + +const StyledTable = styled(Table)` + &&& .ant-table-cell { + padding: 16px; + } +` as typeof Table; + +const ModelsContainer = styled.div` + width: 100%; + padding: 20px; +`; + +const VersionContainer = styled.div` + display: flex; + align-items: center; +`; export default function MLGroupModels() { const baseEntity = useBaseEntity(); - const models = baseEntity?.mlModelGroup?.incoming?.relationships?.map((relationship) => relationship.entity) || []; - const entityRegistry = useEntityRegistry(); + const modelGroup = baseEntity?.mlModelGroup; + + const models = + baseEntity?.mlModelGroup?.incoming?.relationships + ?.map((relationship) => relationship.entity) + .filter(notEmpty) + // eslint-disable-next-line @typescript-eslint/dot-notation + ?.sort((a, b) => b?.['properties']?.createdTS?.time - a?.['properties']?.createdTS?.time) || []; + + const columns = [ + { + title: 'Name', + dataIndex: 'name', + key: 'name', + width: 300, + render: (_: any, record) => ( + + + {record?.properties?.propertiesName || record?.name} + + + ), + }, + { + title: 'Version', + key: 'version', + width: 70, + render: (_: any, record: any) => ( + {record.versionProperties?.version?.versionTag || '-'} + ), + }, + { + title: 'Created At', + key: 'createdAt', + width: 150, + render: (_: any, record: any) => ( + + {record.properties?.createdTS?.time + ? moment(record.properties.createdTS.time).format('YYYY-MM-DD HH:mm:ss') + : '-'} + + ), + }, + { + title: 'Aliases', + key: 'aliases', + width: 200, + render: (_: any, record: any) => { + const aliases = record.versionProperties?.aliases || []; + + return ( + + {aliases.map((alias) => ( + + ))} + + ); + }, + }, + { + title: 'Tags', + key: 'tags', + width: 200, + render: (_: any, record: any) => { + const tags = record.properties?.tags || []; + + return ( + + {tags.map((tag) => ( + + ))} + + ); + }, + }, + { + title: 'Description', + dataIndex: 'description', + key: 'description', + width: 300, + render: (_: any, record: any) => { + const editableDesc = record.editableProperties?.description; + const originalDesc = record.description; + + return {editableDesc || originalDesc || '-'}; + }, + }, + ]; return ( - <> - - Models} - renderItem={(item) => ( - - {entityRegistry.renderPreview(EntityType.Mlmodel, PreviewType.PREVIEW, item)} - - )} - /> - - + + Model Group Details + + + + {modelGroup?.properties?.created?.time + ? moment(modelGroup.properties.created.time).format('YYYY-MM-DD HH:mm:ss') + : '-'} + + + + + {modelGroup?.properties?.lastModified?.time + ? moment(modelGroup.properties.lastModified.time).format('YYYY-MM-DD HH:mm:ss') + : '-'} + + + {modelGroup?.properties?.created?.actor && ( + + {modelGroup.properties.created?.actor} + + )} + + Models + , + }} + /> + ); } diff --git a/datahub-web-react/src/app/entityV2/shared/components/styled/search/EmbeddedListSearchSection.tsx b/datahub-web-react/src/app/entityV2/shared/components/styled/search/EmbeddedListSearchSection.tsx index 2dd17f21e8..e2307d8b3c 100644 --- a/datahub-web-react/src/app/entityV2/shared/components/styled/search/EmbeddedListSearchSection.tsx +++ b/datahub-web-react/src/app/entityV2/shared/components/styled/search/EmbeddedListSearchSection.tsx @@ -4,13 +4,13 @@ import { useHistory, useLocation } from 'react-router'; import { ApolloError } from '@apollo/client'; import useSortInput from '@src/app/searchV2/sorting/useSortInput'; import { useSelectedSortOption } from '@src/app/search/context/SearchContext'; -import { EntityType, FacetFilterInput } from '../../../../../../types.generated'; +import { FacetFilterInput } from '../../../../../../types.generated'; import useFilters from '../../../../../search/utils/useFilters'; import { navigateToEntitySearchUrl } from './navigateToEntitySearchUrl'; import { FilterSet, GetSearchResultsParams, SearchResultsInterface } from './types'; import { useEntityQueryParams } from '../../../containers/profile/utils'; import { EmbeddedListSearch } from './EmbeddedListSearch'; -import { UnionType } from '../../../../../search/utils/constants'; +import { EMBEDDED_LIST_SEARCH_ENTITY_TYPES, UnionType } from '../../../../../search/utils/constants'; import { DownloadSearchResults, DownloadSearchResultsInput, @@ -19,30 +19,6 @@ import { import { decodeComma } from '../../../utils'; const FILTER = 'filter'; -const SEARCH_ENTITY_TYPES = [ - EntityType.Dataset, - EntityType.Dashboard, - EntityType.Chart, - EntityType.Mlmodel, - EntityType.MlmodelGroup, - EntityType.MlfeatureTable, - EntityType.Mlfeature, - EntityType.MlprimaryKey, - EntityType.DataFlow, - EntityType.DataJob, - EntityType.GlossaryTerm, - EntityType.GlossaryNode, - EntityType.Tag, - EntityType.Role, - EntityType.CorpUser, - EntityType.CorpGroup, - EntityType.Container, - EntityType.Domain, - EntityType.DataProduct, - EntityType.Notebook, - EntityType.BusinessAttribute, - EntityType.DataProcessInstance, -]; function getParamsWithoutFilters(params: QueryString.ParsedQuery) { const paramsCopy = { ...params }; @@ -170,7 +146,7 @@ export const EmbeddedListSearchSection = ({ return ( @@ -236,7 +229,7 @@ export const DefaultEntityHeader = ({ type={displayedEntityType} entityType={entityType} browsePaths={entityData?.browsePathV2} - parentEntities={parentEntities} + parentEntities={contextPath} contentRef={contentRef} isContentTruncated={isContentTruncated} /> 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 new file mode 100644 index 0000000000..c3045d61f7 --- /dev/null +++ b/datahub-web-react/src/app/entityV2/shared/containers/profile/header/getContextPath.test.ts @@ -0,0 +1,102 @@ +import { GenericEntityProperties } from '@app/entity/shared/types'; +import { dataPlatform } from '@src/Mocks'; +import { EntityType } from '@types'; +import { getContextPath } from './getContextPath'; + +const PARENT_CONTAINERS: GenericEntityProperties['parentContainers'] = { + containers: [ + { + urn: 'urn:li:container:1', + type: EntityType.Container, + platform: dataPlatform, + }, + { + urn: 'urn:li:container:2', + type: EntityType.Container, + platform: dataPlatform, + }, + ], + count: 2, +}; + +const PARENT_DOMAINS: GenericEntityProperties['parentDomains'] = { + domains: [ + { urn: 'urn:li:domain:1', type: EntityType.Domain }, + { urn: 'urn:li:domain:2', type: EntityType.Domain }, + ], + count: 2, +}; + +const PARENT_NODES: GenericEntityProperties['parentNodes'] = { + nodes: [ + { urn: 'urn:li:glossaryNode:1', type: EntityType.GlossaryNode }, + { + urn: 'urn:li:glossaryNode:2', + type: EntityType.GlossaryNode, + }, + ], + count: 2, +}; + +const PARENT: GenericEntityProperties = { + urn: 'urn:li:dataset:(urn:li:dataPlatform:snowflake,name,PROD)', + type: EntityType.Dataset, + platform: dataPlatform, +}; + +describe('getContextPath', () => { + it('returns empty array by default', () => { + const entityData = {}; + + const contextPath = getContextPath(entityData); + expect(contextPath).toEqual([]); + }); + + it('returns correct context path for entity with parent containers', () => { + const entityData = { + parentContainers: PARENT_CONTAINERS, + parentDomains: PARENT_DOMAINS, + parentNodes: PARENT_NODES, + parent: PARENT, + }; + + const contextPath = getContextPath(entityData); + expect(contextPath).toEqual(PARENT_CONTAINERS.containers); + }); + + it('returns correct context path for entity with parent domains', () => { + const entityData = { + parentContainers: null, + parentDomains: PARENT_DOMAINS, + parentNodes: PARENT_NODES, + parent: PARENT, + }; + + const contextPath = getContextPath(entityData); + expect(contextPath).toEqual(PARENT_DOMAINS.domains); + }); + + it('returns correct context path for entity with parent nodes', () => { + const entityData = { + parentContainers: null, + parentDomains: null, + parentNodes: PARENT_NODES, + parent: PARENT, + }; + + const contextPath = getContextPath(entityData); + expect(contextPath).toEqual(PARENT_NODES.nodes); + }); + + it('returns correct context path for entity with parent', () => { + const entityData = { + parentContainers: null, + parentDomains: null, + parentNodes: null, + parent: PARENT, + }; + + const contextPath = getContextPath(entityData); + expect(contextPath).toEqual([PARENT]); + }); +}); diff --git a/datahub-web-react/src/app/entityV2/shared/containers/profile/header/getContextPath.ts b/datahub-web-react/src/app/entityV2/shared/containers/profile/header/getContextPath.ts new file mode 100644 index 0000000000..ac7f5493ef --- /dev/null +++ b/datahub-web-react/src/app/entityV2/shared/containers/profile/header/getContextPath.ts @@ -0,0 +1,17 @@ +import { GenericEntityProperties } from '@app/entity/shared/types'; +import { Entity } from '@types'; + +type GetContextPathInput = Pick< + GenericEntityProperties, + 'parent' | 'parentContainers' | 'parentDomains' | 'parentNodes' +>; + +export function getContextPath(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; +} diff --git a/datahub-web-react/src/app/preview/DataProcessInstanceRightColumn.tsx b/datahub-web-react/src/app/preview/DataProcessInstanceRightColumn.tsx index ba4990836c..ad9550456f 100644 --- a/datahub-web-react/src/app/preview/DataProcessInstanceRightColumn.tsx +++ b/datahub-web-react/src/app/preview/DataProcessInstanceRightColumn.tsx @@ -5,7 +5,6 @@ import { toRelativeTimeString, } from '@app/shared/time/timeUtils'; import { Pill, Popover } from '@components'; -import { Maybe } from 'graphql/jsutils/Maybe'; import { capitalize } from 'lodash'; import React from 'react'; import styled from 'styled-components'; @@ -43,9 +42,9 @@ const popoverStyles = { }; interface Props { - startTime: Maybe; - duration: Maybe; - status: Maybe; + startTime?: number; + duration?: number; + status?: string; } export default function DataProcessInstanceRightColumn({ startTime, duration, status }: Props) { diff --git a/datahub-web-react/src/app/preview/DefaultPreviewCard.tsx b/datahub-web-react/src/app/preview/DefaultPreviewCard.tsx index 42a32a5a19..af1268e14d 100644 --- a/datahub-web-react/src/app/preview/DefaultPreviewCard.tsx +++ b/datahub-web-react/src/app/preview/DefaultPreviewCard.tsx @@ -200,9 +200,11 @@ interface Props { paths?: EntityPath[]; health?: Health[]; parentDataset?: Dataset; - startTime?: number | null; - duration?: number | null; - status?: string | null; + dataProcessInstanceProps?: { + startTime?: number; + duration?: number; + status?: string; + }; } export default function DefaultPreviewCard({ @@ -246,9 +248,7 @@ export default function DefaultPreviewCard({ paths, health, parentDataset, - startTime, - duration, - status, + dataProcessInstanceProps, }: Props) { // sometimes these lists will be rendered inside an entity container (for example, in the case of impact analysis) // in those cases, we may want to enrich the preview w/ context about the container entity @@ -277,7 +277,11 @@ export default function DefaultPreviewCard({ }; const shouldShowRightColumn = - (topUsers && topUsers.length > 0) || (owners && owners.length > 0) || startTime || duration || status; + (topUsers && topUsers.length > 0) || + (owners && owners.length > 0) || + dataProcessInstanceProps?.startTime || + dataProcessInstanceProps?.duration || + dataProcessInstanceProps?.status; const uniqueOwners = getUniqueOwners(owners); return ( @@ -387,7 +391,7 @@ export default function DefaultPreviewCard({ {shouldShowRightColumn && ( - + {topUsers && topUsers?.length > 0 && ( <> diff --git a/datahub-web-react/src/app/search/utils/constants.ts b/datahub-web-react/src/app/search/utils/constants.ts index 6a06ef68f7..ea3d80891b 100644 --- a/datahub-web-react/src/app/search/utils/constants.ts +++ b/datahub-web-react/src/app/search/utils/constants.ts @@ -1,3 +1,5 @@ +import { EntityType } from '@types'; + export const FILTER_URL_PREFIX = 'filter_'; export const SEARCH_FOR_ENTITY_PREFIX = 'SEARCH__'; export const EXACT_SEARCH_PREFIX = 'EXACT__'; @@ -140,3 +142,28 @@ export const FilterModes = { export type FilterMode = (typeof FilterModes)[keyof typeof FilterModes]; export const MAX_COUNT_VAL = 10000; + +export const EMBEDDED_LIST_SEARCH_ENTITY_TYPES = [ + EntityType.Dataset, + EntityType.Dashboard, + EntityType.Chart, + EntityType.Mlmodel, + EntityType.MlmodelGroup, + EntityType.MlfeatureTable, + EntityType.Mlfeature, + EntityType.MlprimaryKey, + EntityType.DataFlow, + EntityType.DataJob, + EntityType.GlossaryTerm, + EntityType.GlossaryNode, + EntityType.Tag, + EntityType.Role, + EntityType.CorpUser, + EntityType.CorpGroup, + EntityType.Container, + EntityType.Domain, + EntityType.DataProduct, + EntityType.Notebook, + EntityType.BusinessAttribute, + EntityType.DataProcessInstance, +];