;
description?: string;
columnConstraint?: Constraint;
tableConstraints?: TableConstraint[];
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/TableSummary/TableSummary.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/TableSummary/TableSummary.component.tsx
index 2c26d6d7292..755085315af 100644
--- a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/TableSummary/TableSummary.component.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/TableSummary/TableSummary.component.tsx
@@ -247,15 +247,10 @@ function TableSummary({
entityDetail={tableDetails}
tags={
tags ??
- getSortedTagsWithHighlight({
- tags: tableDetails.tags,
- sortTagsBasedOnGivenTagFQNs: get(
- highlights,
- 'tag.name',
- [] as string[]
- ),
- }) ??
- []
+ getSortedTagsWithHighlight(
+ tableDetails.tags,
+ get(highlights, 'tag.name')
+ )
}
/>
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/TopicSummary/TopicSummary.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/TopicSummary/TopicSummary.component.tsx
index 1b2bae4083a..91b07eff908 100644
--- a/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/TopicSummary/TopicSummary.component.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/components/Explore/EntitySummaryPanel/TopicSummary/TopicSummary.component.tsx
@@ -167,15 +167,10 @@ function TopicSummary({
entityDetail={entityDetails}
tags={
tags ??
- getSortedTagsWithHighlight({
- tags: entityDetails.tags,
- sortTagsBasedOnGivenTagFQNs: get(
- highlights,
- 'tag.name',
- [] as string[]
- ),
- }) ??
- []
+ getSortedTagsWithHighlight(
+ entityDetails.tags,
+ get(highlights, 'tag.name')
+ )
}
/>
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Tag/TagsV1/TagsV1.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Tag/TagsV1/TagsV1.component.tsx
index 2aba33ef2c7..66aab57f39f 100644
--- a/openmetadata-ui/src/main/resources/ui/src/components/Tag/TagsV1/TagsV1.component.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/components/Tag/TagsV1/TagsV1.component.tsx
@@ -22,11 +22,11 @@ import { ROUTES } from '../../../constants/constants';
import { TAG_START_WITH } from '../../../constants/Tag.constants';
import { TagSource } from '../../../generated/type/tagLabel';
import { reduceColorOpacity } from '../../../utils/CommonUtils';
-import { HighlightedTagLabel } from '../../../utils/EntitySummaryPanelUtils';
import { getEntityName } from '../../../utils/EntityUtils';
import Fqn from '../../../utils/Fqn';
import { getEncodedFqn } from '../../../utils/StringsUtils';
import { getTagDisplay, getTagTooltip } from '../../../utils/TagsUtils';
+import { HighlightedTagLabel } from '../../Explore/EntitySummaryPanel/SummaryList/SummaryList.interface';
import { TagsV1Props } from './TagsV1.interface';
import './tagsV1.less';
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Tag/TagsV1/TagsV1.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/Tag/TagsV1/TagsV1.interface.ts
index 9524dcef9aa..b6602c42df0 100644
--- a/openmetadata-ui/src/main/resources/ui/src/components/Tag/TagsV1/TagsV1.interface.ts
+++ b/openmetadata-ui/src/main/resources/ui/src/components/Tag/TagsV1/TagsV1.interface.ts
@@ -14,7 +14,7 @@
import { TagProps } from 'antd';
import { TAG_START_WITH } from '../../../constants/Tag.constants';
import { TagLabel, TagSource } from '../../../generated/type/tagLabel';
-import { HighlightedTagLabel } from '../../../utils/EntitySummaryPanelUtils';
+import { HighlightedTagLabel } from '../../Explore/EntitySummaryPanel/SummaryList/SummaryList.interface';
export type TagsV1Props = {
tag: TagLabel | HighlightedTagLabel;
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/SummaryTagsDescription/SummaryTagsDescription.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/SummaryTagsDescription/SummaryTagsDescription.component.tsx
index 7794764585a..59b3c3b7d6d 100644
--- a/openmetadata-ui/src/main/resources/ui/src/components/common/SummaryTagsDescription/SummaryTagsDescription.component.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/components/common/SummaryTagsDescription/SummaryTagsDescription.component.tsx
@@ -14,15 +14,15 @@ import { Col, Divider, Row, Typography } from 'antd';
import React from 'react';
import { useTranslation } from 'react-i18next';
import TagsViewer from '../../../components/Tag/TagsViewer/TagsViewer';
-import { TagLabel } from '../../../generated/type/tagLabel';
+import { BasicEntityInfo } from '../../Explore/EntitySummaryPanel/SummaryList/SummaryList.interface';
import { EntityUnion } from '../../Explore/ExplorePage.interface';
import RichTextEditorPreviewer from '../RichTextEditor/RichTextEditorPreviewer';
const SummaryTagsDescription = ({
- tags,
+ tags = [],
entityDetail,
}: {
- tags: TagLabel[];
+ tags: BasicEntityInfo['tags'];
entityDetail: EntityUnion;
}) => {
const { t } = useTranslation();
diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/EntitySummaryPanelUtils.test.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/EntitySummaryPanelUtils.test.tsx
index 53ecc638b3d..e7f9fbf16d3 100644
--- a/openmetadata-ui/src/main/resources/ui/src/utils/EntitySummaryPanelUtils.test.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/utils/EntitySummaryPanelUtils.test.tsx
@@ -15,6 +15,8 @@ import { SummaryEntityType } from '../enums/EntitySummary.enum';
import { Column } from '../generated/entity/data/table';
import {
getFormattedEntityData,
+ getHighlightOfListItem,
+ getMapOfListHighlights,
getSortedTagsWithHighlight,
getSummaryListItemType,
getTitle,
@@ -24,87 +26,146 @@ import {
mockEntityDataWithNestingResponse,
mockEntityDataWithoutNesting,
mockEntityDataWithoutNestingResponse,
+ mockGetHighlightOfListItemResponse,
+ mockGetMapOfListHighlightsResponse,
mockGetSummaryListItemTypeResponse,
+ mockHighlights,
mockInvalidDataResponse,
mockLinkBasedSummaryTitleResponse,
+ mockListItemNameHighlight,
mockTagFQNsForHighlight,
- mockTagsDataAfterSortAndHighlight,
- mockTagsDataBeforeSortAndHighlight,
+ mockTagsSortAndHighlightResponse,
mockTextBasedSummaryTitleResponse,
} from './mocks/EntitySummaryPanelUtils.mock';
+jest.mock('../constants/EntitySummaryPanelUtils.constant', () => ({
+ ...jest.requireActual('../constants/EntitySummaryPanelUtils.constant'),
+ SummaryListHighlightKeys: [
+ 'columns.name',
+ 'columns.description',
+ 'columns.children.name',
+ ],
+}));
+
describe('EntitySummaryPanelUtils tests', () => {
- it('getFormattedEntityData should return formatted data properly for table columns data without nesting, and also sort the data based on given arr', () => {
- const highlights = {
- 'tag.name': ['PersonalData.SpecialCategory'],
- };
- const resultFormattedData = getFormattedEntityData(
- SummaryEntityType.COLUMN,
- mockEntityDataWithoutNesting,
- highlights
- );
+ describe('getFormattedEntityData', () => {
+ it('getFormattedEntityData should return formatted data properly for table columns data with nesting, and also sort the data based on highlights', () => {
+ const resultFormattedData = getFormattedEntityData(
+ SummaryEntityType.COLUMN,
+ mockEntityDataWithNesting,
+ mockHighlights
+ );
- expect(resultFormattedData).toEqual(mockEntityDataWithoutNestingResponse);
- });
-
- it('getFormattedEntityData should return formatted data properly for topic fields data with nesting', () => {
- const resultFormattedData = getFormattedEntityData(
- SummaryEntityType.COLUMN,
- mockEntityDataWithNesting
- );
-
- expect(resultFormattedData).toEqual(mockEntityDataWithNestingResponse);
- });
-
- it('getFormattedEntityData should return empty array in case entityType is given other than from type SummaryEntityType', () => {
- const resultFormattedData = getFormattedEntityData(
- 'otherType' as SummaryEntityType,
- mockEntityDataWithNesting
- );
-
- expect(resultFormattedData).toEqual([]);
- });
-
- it('getFormattedEntityData should not throw error if entityDetails sent does not have fields present', () => {
- const resultFormattedData = getFormattedEntityData(
- SummaryEntityType.COLUMN,
- [{}] as Column[]
- );
-
- expect(resultFormattedData).toEqual(mockInvalidDataResponse);
- });
-
- it('getSortedTagsWithHighlight should return the sorted and highlighted tags data based on given tagFQN array', () => {
- const sortedTags = getSortedTagsWithHighlight({
- sortTagsBasedOnGivenTagFQNs: mockTagFQNsForHighlight,
- tags: mockTagsDataBeforeSortAndHighlight,
+ expect(resultFormattedData).toEqual(mockEntityDataWithNestingResponse);
});
- expect(sortedTags).toEqual(mockTagsDataAfterSortAndHighlight);
- });
+ it('getFormattedEntityData should return formatted data properly for pipeline data without nesting', () => {
+ const resultFormattedData = getFormattedEntityData(
+ SummaryEntityType.TASK,
+ mockEntityDataWithoutNesting
+ );
- it('getSummaryListItemType should return the summary item type based on given entityType', () => {
- const summaryItemType = getSummaryListItemType(
- SummaryEntityType.COLUMN,
- mockEntityDataWithoutNesting[0]
- );
-
- expect(summaryItemType).toEqual(mockGetSummaryListItemTypeResponse);
- });
-
- it('getTitle should return title as link or text based on sourceUrl present or not in given data', () => {
- const textBasedTitle = getTitle({
- content: 'Title1',
- sourceUrl: undefined,
+ expect(resultFormattedData).toEqual(mockEntityDataWithoutNestingResponse);
});
- expect(textBasedTitle).toEqual(mockTextBasedSummaryTitleResponse);
+ it('getFormattedEntityData should return empty array in case entityType is given other than from type SummaryEntityType', () => {
+ const resultFormattedData = getFormattedEntityData(
+ 'otherType' as SummaryEntityType,
+ mockEntityDataWithNesting
+ );
- const linkBasedTitle = getTitle({
- content: 'Title2',
- sourceUrl: 'https://task1.com',
+ expect(resultFormattedData).toEqual([]);
});
- expect(linkBasedTitle).toEqual(mockLinkBasedSummaryTitleResponse);
+ it('getFormattedEntityData should not throw error if entityDetails sent does not have fields present', () => {
+ const resultFormattedData = getFormattedEntityData(
+ SummaryEntityType.COLUMN,
+ [{}] as Column[]
+ );
+
+ expect(resultFormattedData).toEqual(mockInvalidDataResponse);
+ });
+ });
+
+ describe('getSortedTagsWithHighlight', () => {
+ it('getSortedTagsWithHighlight should return the sorted and highlighted tags data based on given tagFQN array', () => {
+ const sortedTags = getSortedTagsWithHighlight(
+ mockEntityDataWithNesting[2].tags,
+ mockTagFQNsForHighlight
+ );
+
+ expect(sortedTags).toEqual(mockTagsSortAndHighlightResponse);
+ });
+ });
+
+ describe('getSummaryListItemType', () => {
+ it('getSummaryListItemType should return the summary item type based on given entityType', () => {
+ const summaryItemType = getSummaryListItemType(
+ SummaryEntityType.TASK,
+ mockEntityDataWithoutNesting[0]
+ );
+
+ expect(summaryItemType).toEqual(mockGetSummaryListItemTypeResponse);
+ });
+ });
+
+ describe('getTitle', () => {
+ it('getTitle should return title as text if sourceUrl not present in listItem and also apply highlight if present', () => {
+ const textBasedTitle = getTitle(
+ mockEntityDataWithNesting[0],
+ mockListItemNameHighlight
+ );
+
+ expect(textBasedTitle).toEqual(mockTextBasedSummaryTitleResponse);
+ });
+
+ it('getTitle should return title as link if sourceUrl present in listItem', () => {
+ const linkBasedTitle = getTitle(mockEntityDataWithoutNesting[0]);
+
+ expect(linkBasedTitle).toEqual(mockLinkBasedSummaryTitleResponse);
+ });
+ });
+
+ describe('getMapOfListHighlights', () => {
+ it('getMapOfListHighlights should returns empty arrays and map when highlights is undefined', () => {
+ const result = getMapOfListHighlights();
+
+ expect(result.listHighlights).toEqual([]);
+ expect(result.listHighlightsMap).toEqual({});
+ });
+
+ it('getMapOfListHighlights should returns listHighlights and listHighlightsMap correctly', () => {
+ const result = getMapOfListHighlights(mockHighlights);
+
+ expect(result).toEqual(mockGetMapOfListHighlightsResponse);
+ });
+ });
+
+ describe('getHighlightOfListItem', () => {
+ it('getHighlightOfListItem should return highlights of listItem undefined, if listHighlights and tagHighlights not passed in params', () => {
+ const result = getHighlightOfListItem(
+ mockEntityDataWithNesting[0],
+ [] as string[],
+ [] as string[],
+ {} as { [key: string]: number }
+ );
+
+ expect(result).toEqual({
+ highlightedTags: undefined,
+ highlightedTitle: undefined,
+ highlightedDescription: undefined,
+ });
+ });
+
+ it('getHighlightOfListItem should return highlights of listItem if listHighlights and tagHighlights get in params', () => {
+ const result = getHighlightOfListItem(
+ mockEntityDataWithNesting[1],
+ mockTagFQNsForHighlight,
+ mockGetMapOfListHighlightsResponse.listHighlights,
+ mockGetMapOfListHighlightsResponse.listHighlightsMap
+ );
+
+ expect(result).toEqual(mockGetHighlightOfListItemResponse);
+ });
});
});
diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/EntitySummaryPanelUtils.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/EntitySummaryPanelUtils.tsx
index 61c3f31d877..f4b9b06d666 100644
--- a/openmetadata-ui/src/main/resources/ui/src/utils/EntitySummaryPanelUtils.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/utils/EntitySummaryPanelUtils.tsx
@@ -17,7 +17,10 @@ import React from 'react';
import { Link } from 'react-router-dom';
import { SearchedDataProps } from '../../src/components/SearchedData/SearchedData.interface';
import { ReactComponent as IconExternalLink } from '../assets/svg/external-links.svg';
-import { BasicEntityInfo } from '../components/Explore/EntitySummaryPanel/SummaryList/SummaryList.interface';
+import {
+ BasicEntityInfo,
+ HighlightedTagLabel,
+} from '../components/Explore/EntitySummaryPanel/SummaryList/SummaryList.interface';
import { NO_DATA_PLACEHOLDER } from '../constants/constants';
import { SummaryListHighlightKeys } from '../constants/EntitySummaryPanelUtils.constant';
import { SummaryEntityType } from '../enums/EntitySummary.enum';
@@ -37,20 +40,30 @@ export interface EntityNameProps {
displayName?: string;
}
-export interface HighlightedTagLabel extends TagLabel {
- isHighlighted: boolean;
+export type SummaryListItem = Column | Field | Chart | Task | MlFeature;
+
+export interface ListItemHighlights {
+ highlightedTags?: BasicEntityInfo['tags'];
+ highlightedTitle?: string;
+ highlightedDescription?: string;
}
-const getTitleName = (data: EntityNameProps) =>
- getEntityName(data) || NO_DATA_PLACEHOLDER;
+/* @param {
+ listItem: SummaryItem,
+ highlightedTitle: will be a string if the title of given summaryItem is present in highlights | undefined
+}
+
+ @return SummaryItemTitle
+*/
+export const getTitle = (
+ listItem: SummaryListItem,
+ highlightedTitle?: ListItemHighlights['highlightedTitle']
+): JSX.Element | JSX.Element[] => {
+ const title = highlightedTitle
+ ? stringToHTML(highlightedTitle)
+ : getEntityName(listItem) || NO_DATA_PLACEHOLDER;
+ const sourceUrl = (listItem as Chart | Task).sourceUrl;
-export const getTitle = ({
- content,
- sourceUrl,
-}: {
- content: string | JSX.Element | JSX.Element[] | undefined;
- sourceUrl: string | undefined;
-}) => {
return sourceUrl ? (
@@ -58,65 +71,189 @@ export const getTitle = ({
className="entity-title text-link-color font-medium m-r-xss"
data-testid="entity-title"
ellipsis={{ tooltip: true }}>
- {content}
+ {title}
) : (
- {content}
+ {title}
);
};
+/* @param {
+ entityType: will be any type of SummaryEntityType,
+ listItem: SummaryItem
+}
+ @return listItemType
+*/
export const getSummaryListItemType = (
entityType: SummaryEntityType,
- listItemInfo: Column | Field | Chart | Task | MlFeature
-) => {
+ listItem: SummaryListItem
+): BasicEntityInfo['type'] => {
switch (entityType) {
case SummaryEntityType.COLUMN:
case SummaryEntityType.FIELD:
case SummaryEntityType.MLFEATURE:
case SummaryEntityType.SCHEMAFIELD:
- return (listItemInfo as Column | Field | MlFeature).dataType;
+ return (listItem as Column | Field | MlFeature).dataType;
case SummaryEntityType.CHART:
- return (listItemInfo as Chart).chartType;
+ return (listItem as Chart).chartType;
case SummaryEntityType.TASK:
- return (listItemInfo as Task).taskType;
+ return (listItem as Task).taskType;
default:
return '';
}
};
-export const getSortedTagsWithHighlight = ({
- sortTagsBasedOnGivenTagFQNs,
- tags,
-}: {
- sortTagsBasedOnGivenTagFQNs: string[];
- tags?: TagLabel[];
-}): (TagLabel | HighlightedTagLabel)[] => {
- const ColumnDataTags: {
- tagForSort: HighlightedTagLabel[];
- remainingTags: TagLabel[];
- } = { tagForSort: [], remainingTags: [] };
-
- tags?.reduce((acc, tag) => {
- if (sortTagsBasedOnGivenTagFQNs.includes(tag.tagFQN)) {
- acc.tagForSort.push({ ...tag, isHighlighted: true });
- } else {
- acc.remainingTags.push(tag);
+/*
+ @params {
+ sortTagsBasedOnGivenTagFQNs: array of TagFQNs,
+ tags: Tags array,
}
- return acc;
- }, ColumnDataTags);
+ @return array of tags highlighted and sorted if tagFQN present in sortTagsBasedOnGivenTagFQNs
+*/
+export const getSortedTagsWithHighlight = (
+ tags: TagLabel[] = [],
+ sortTagsBasedOnGivenTagFQNs: string[] = []
+): ListItemHighlights['highlightedTags'] => {
+ const { sortedTags, remainingTags } = tags.reduce(
+ (acc, tag) => {
+ if (sortTagsBasedOnGivenTagFQNs.includes(tag.tagFQN)) {
+ acc.sortedTags.push({ ...tag, isHighlighted: true });
+ } else {
+ acc.remainingTags.push(tag);
+ }
- return [...ColumnDataTags.tagForSort, ...ColumnDataTags.remainingTags];
+ return acc;
+ },
+ {
+ sortedTags: [] as HighlightedTagLabel[],
+ remainingTags: [] as TagLabel[],
+ }
+ );
+
+ return [...sortedTags, ...remainingTags];
};
+/*
+ @param {highlights: all the other highlights come from the query api
+ only omitted displayName and description key as it is already updated in parent component
+ }
+
+ @return {
+ listHighlights: single array of all highlights get from query api
+ listHighlightsMap: to reduce the search time complexity in listHighlight
+ }
+
+ Todo: apply highlights on entityData in parent where we apply highlight for entityDisplayName and entityDescription
+ for that we need to update multiple summary components
+*/
+export const getMapOfListHighlights = (
+ highlights?: SearchedDataProps['data'][number]['highlight']
+): {
+ listHighlights: string[];
+ listHighlightsMap: { [key: string]: number };
+} => {
+ // checking for the all highlight key present in highlight get from query api
+ // and create a array of highlights
+ const listHighlights: string[] = [];
+ SummaryListHighlightKeys.forEach((highlightKey) => {
+ listHighlights.push(...get(highlights, highlightKey, []));
+ });
+
+ // using hashmap methodology to reduce the search time complexity from O(n) to O(1)
+ // to get highlight from the listHighlights array for applying highlight
+ const listHighlightsMap: { [key: string]: number } = {};
+
+ listHighlights?.reduce((acc, colHighlight, index) => {
+ acc[colHighlight.replaceAll(/<\/?span(.*?)>/g, '')] = index;
+
+ return acc;
+ }, listHighlightsMap);
+
+ return { listHighlights, listHighlightsMap };
+};
+
+/*
+ @params {
+ listItem: SummaryItem
+ tagsHighlights: tagFQNs array to highlight and sort tags
+ listHighlights: single array of all highlights get from query api
+ listHighlightsMap: to reduce the search time complexity in listHighlight
+ }
+ @return highlights of listItem
+*/
+export const getHighlightOfListItem = (
+ listItem: SummaryListItem,
+ tagHighlights: string[],
+ listHighlights: string[],
+ listHighlightsMap: { [key: string]: number }
+): ListItemHighlights => {
+ let highlightedTags;
+ let highlightedTitle;
+ let highlightedDescription;
+
+ // if any of the listItem.tags present in given tagHighlights list then sort and highlights the tag
+ const shouldSortListItemTags = listItem.tags?.find((tag) =>
+ tagHighlights.includes(tag.tagFQN)
+ );
+
+ if (shouldSortListItemTags) {
+ highlightedTags = getSortedTagsWithHighlight(listItem.tags, tagHighlights);
+ }
+
+ // highlightedListItemNameIndex will be undefined if listItem.name is not present in highlights
+ const highlightedListItemNameIndex = listHighlightsMap[listItem.name ?? ''];
+
+ const shouldApplyHighlightOnTitle = !isUndefined(
+ highlightedListItemNameIndex
+ );
+
+ if (shouldApplyHighlightOnTitle) {
+ highlightedTitle = listHighlights[highlightedListItemNameIndex];
+ }
+
+ // highlightedListItemDescriptionIndex will be undefined if listItem.description is not present in highlights
+ const highlightedListItemDescriptionIndex =
+ listHighlightsMap[listItem.description ?? ''];
+
+ const shouldApplyHighlightOnDescription = !isUndefined(
+ highlightedListItemDescriptionIndex
+ );
+
+ if (shouldApplyHighlightOnDescription) {
+ highlightedDescription =
+ listHighlights[highlightedListItemDescriptionIndex];
+ }
+
+ return {
+ highlightedTags,
+ highlightedTitle,
+ highlightedDescription,
+ };
+};
+
+/*
+ @params {
+ entityType: SummaryEntityType,
+ entityInfo: Array = [],
+ highlights: highlights get from the query api + highlights added for tags (i.e. tag.name)
+ tableConstraints: only pass for SummayEntityType.Column
+ }
+ @return sorted and highlighted listItem array, but listItem will be type of BasicEntityInfo
+
+ Note: SummaryItem will be sort and highlight only if -
+ # if listItem.tags present in highlights.tags
+ # if listItem.name present in highlights comes from query api
+ # if listItem.description present in highlights comes from query api
+*/
export const getFormattedEntityData = (
entityType: SummaryEntityType,
- entityInfo?: Array,
+ entityInfo: Array = [],
highlights?: SearchedDataProps['data'][number]['highlight'],
tableConstraints?: TableConstraint[]
): BasicEntityInfo[] => {
@@ -124,97 +261,68 @@ export const getFormattedEntityData = (
return [];
}
- // sort and highlights list items based on tags and global search highlights data
+ // Only go ahead if entityType is present in SummaryEntityType enum
if (Object.values(SummaryEntityType).includes(entityType)) {
+ // tagHighlights is the array of tagFQNs for highlighting tags
const tagHighlights = get(highlights, 'tag.name', [] as string[]);
- const listHighlights: string[] = [];
- const listHighlightsMap: { [key: string]: number } = {};
- const SummaryListData = {
- listItemWithSortOption: [] as BasicEntityInfo[],
- listItemWithoutSortOption: [] as BasicEntityInfo[],
- };
- SummaryListHighlightKeys.forEach((highlightKey) => {
- listHighlights.push(...get(highlights, highlightKey, []));
- });
+ // listHighlights i.e. highlight get from query api
+ // listHighlightsMap i.e. map of highlight get from api to reduce search time complexity in highlights array
+ const { listHighlights, listHighlightsMap } =
+ getMapOfListHighlights(highlights);
- listHighlights?.reduce((acc, colHighlight, index) => {
- acc[colHighlight.replaceAll(/<\/?span(.*?)>/g, '')] = index;
+ const { highlightedListItem, remainingListItem } = entityInfo.reduce(
+ (acc, listItem) => {
+ // return the highlight of listItem
+ const { highlightedTags, highlightedTitle, highlightedDescription } =
+ getHighlightOfListItem(
+ listItem,
+ tagHighlights,
+ listHighlights,
+ listHighlightsMap
+ );
- return acc;
- }, listHighlightsMap);
+ // convert listItem in BasicEntityInfo type
+ const listItemModifiedData = {
+ name: listItem.name ?? '',
+ title: getTitle(listItem, highlightedTitle),
+ type: getSummaryListItemType(entityType, listItem),
+ tags: highlightedTags ?? listItem.tags,
+ description: highlightedDescription ?? listItem.description,
+ ...(entityType === SummaryEntityType.COLUMN && {
+ columnConstraint: (listItem as Column).constraint,
+ tableConstraints: tableConstraints,
+ }),
+ ...(entityType === SummaryEntityType.MLFEATURE && {
+ algorithm: (listItem as MlFeature).featureAlgorithm,
+ }),
+ ...((entityType === SummaryEntityType.COLUMN ||
+ entityType === SummaryEntityType.FIELD) && {
+ children: getFormattedEntityData(
+ entityType,
+ (listItem as Column | Field).children,
+ highlights
+ ),
+ }),
+ };
- entityInfo?.reduce((acc, listItem) => {
- const listItemModifiedData = {
- name: listItem.name ?? '',
- title: getTitle({
- content: getTitleName(listItem),
- sourceUrl: (listItem as Chart | Task).sourceUrl,
- }),
- type: getSummaryListItemType(entityType, listItem),
- tags: listItem.tags,
- description: listItem.description,
- ...(entityType === SummaryEntityType.COLUMN && {
- columnConstraint: (listItem as Column).constraint,
- tableConstraints: tableConstraints,
- }),
- ...(entityType === SummaryEntityType.MLFEATURE && {
- algorithm: (listItem as MlFeature).featureAlgorithm,
- }),
- children: getFormattedEntityData(
- entityType,
- (listItem as Column | Field).children,
- highlights,
- tableConstraints
- ),
- };
-
- const isTagHighlightsPresentInListItemTags = listItem.tags?.find((tag) =>
- tagHighlights.includes(tag.tagFQN)
- );
-
- const highlightedListItemNameIndex =
- listHighlightsMap[listItem.name ?? ''];
- const highlightedListItemDescriptionIndex =
- listHighlightsMap[listItem.description ?? ''];
-
- if (
- isTagHighlightsPresentInListItemTags ||
- !isUndefined(highlightedListItemNameIndex) ||
- !isUndefined(highlightedListItemDescriptionIndex)
- ) {
- if (isTagHighlightsPresentInListItemTags) {
- listItemModifiedData.tags = getSortedTagsWithHighlight({
- sortTagsBasedOnGivenTagFQNs: tagHighlights,
- tags: listItem.tags,
- });
+ // if highlights present in listItem then sort the listItem
+ if (highlightedTags || highlightedTitle || highlightedDescription) {
+ acc.highlightedListItem.push(listItemModifiedData);
+ } else {
+ acc.remainingListItem.push(listItemModifiedData);
}
- if (!isUndefined(highlightedListItemNameIndex)) {
- listItemModifiedData.title = getTitle({
- content: stringToHTML(listHighlights[highlightedListItemNameIndex]),
- sourceUrl: (listItem as Chart | Task).sourceUrl,
- });
- }
-
- if (!isUndefined(highlightedListItemDescriptionIndex)) {
- listItemModifiedData.description =
- listHighlights[highlightedListItemDescriptionIndex];
- }
-
- acc.listItemWithSortOption.push(listItemModifiedData);
- } else {
- acc.listItemWithoutSortOption.push(listItemModifiedData);
+ return acc;
+ },
+ {
+ highlightedListItem: [] as BasicEntityInfo[],
+ remainingListItem: [] as BasicEntityInfo[],
}
+ );
- return acc;
- }, SummaryListData);
-
- return [
- ...SummaryListData.listItemWithSortOption,
- ...SummaryListData.listItemWithoutSortOption,
- ];
- } else {
- return [];
+ return [...highlightedListItem, ...remainingListItem];
}
+
+ return [];
};
diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/mocks/EntitySummaryPanelUtils.mock.tsx b/openmetadata-ui/src/main/resources/ui/src/utils/mocks/EntitySummaryPanelUtils.mock.tsx
index 84f48aa15d0..8b3edaa9d4b 100644
--- a/openmetadata-ui/src/main/resources/ui/src/utils/mocks/EntitySummaryPanelUtils.mock.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/utils/mocks/EntitySummaryPanelUtils.mock.tsx
@@ -14,6 +14,8 @@
import { Typography } from 'antd';
import React from 'react';
import { Link } from 'react-router-dom';
+import { BasicEntityInfo } from '../../components/Explore/EntitySummaryPanel/SummaryList/SummaryList.interface';
+import { Task } from '../../generated/entity/data/pipeline';
import {
Column,
DataType,
@@ -21,21 +23,189 @@ import {
State,
TagSource,
} from '../../generated/entity/data/table';
-import { DataTypeTopic, Field } from '../../generated/entity/data/topic';
import { ReactComponent as IconExternalLink } from '../assets/svg/external-links.svg';
const { Text } = Typography;
-export const mockEntityDataWithoutNesting: Column[] = [
+export const mockTextBasedSummaryTitleResponse = (
+
+ title2
+
+);
+
+export const mockLinkBasedSummaryTitleResponse = (
+
+
+
+ dim_address Task
+
+
+
+
+);
+
+export const mockGetSummaryListItemTypeResponse = 'PrestoOperator';
+
+export const mockTagsSortAndHighlightResponse = [
{
- name: 'title',
+ tagFQN: 'PersonalData.SpecialCategory',
+ description:
+ 'GDPR special category data is personal information of data subjects that is especially sensitive.',
+ source: TagSource.Classification,
+ labelType: LabelType.Manual,
+ state: State.Confirmed,
+ isHighlighted: true,
+ },
+ {
+ tagFQN: 'PersonalData.Category1',
+ description:
+ 'GDPR special category data is personal information of data subjects that is especially sensitive.',
+ source: TagSource.Classification,
+ labelType: LabelType.Manual,
+ state: State.Confirmed,
+ },
+];
+
+export const mockTagFQNsForHighlight = ['PersonalData.SpecialCategory'];
+
+export const mockListItemNameHighlight =
+ 'title2';
+
+const mockListItemDescriptionHighlight =
+ 'some description of title2';
+
+export const mockHighlights = {
+ 'columns.name': [mockListItemNameHighlight],
+ 'columns.description': [mockListItemDescriptionHighlight],
+ 'tag.name': mockTagFQNsForHighlight,
+};
+
+export const mockGetMapOfListHighlightsResponse = {
+ listHighlights: [mockListItemNameHighlight, mockListItemDescriptionHighlight],
+ listHighlightsMap: {
+ title2: 0,
+ 'some description of title2': 1,
+ },
+};
+
+export const mockGetHighlightOfListItemResponse = {
+ highlightedTags: undefined,
+ highlightedTitle: mockListItemNameHighlight,
+ highlightedDescription: mockListItemDescriptionHighlight,
+};
+
+export const mockEntityDataWithoutNesting: Task[] = [
+ {
+ name: 'dim_address_task',
+ displayName: 'dim_address Task',
+ fullyQualifiedName: 'sample_airflow.dim_address_etl.dim_address_task',
+ description:
+ 'Airflow operator to perform ETL and generate dim_address table',
+ sourceUrl:
+ 'http://localhost:8080/taskinstance/list/?flt1_dag_id_equals=dim_address_task',
+ downstreamTasks: ['assert_table_exists'],
+ taskType: 'PrestoOperator',
+ tags: [],
+ },
+ {
+ name: 'assert_table_exists',
+ displayName: 'Assert Table Exists',
+ fullyQualifiedName: 'sample_airflow.dim_address_etl.assert_table_exists',
+ description: 'Assert if a table exists',
+ sourceUrl:
+ 'http://localhost:8080/taskinstance/list/?flt1_dag_id_equals=assert_table_exists',
+ downstreamTasks: [],
+ taskType: 'HiveOperator',
+ tags: [],
+ },
+];
+
+export const mockEntityDataWithoutNestingResponse: BasicEntityInfo[] = [
+ {
+ name: 'dim_address_task',
+ title: mockLinkBasedSummaryTitleResponse,
+ description:
+ 'Airflow operator to perform ETL and generate dim_address table',
+ type: mockGetSummaryListItemTypeResponse,
+ tags: [],
+ },
+ {
+ name: 'assert_table_exists',
+ title: (
+
+
+
+ Assert Table Exists
+
+
+
+
+ ),
+ description: 'Assert if a table exists',
+ type: 'HiveOperator',
+ tags: [],
+ },
+];
+
+export const mockEntityDataWithNesting: Column[] = [
+ {
+ name: 'Customer',
+ dataType: DataType.Varchar,
+ fullyQualifiedName: 'sample_kafka.customer_events.Customer',
+ tags: [],
+ description:
+ 'Full name of the app or channel. For example, Point of Sale, Online Store.',
+ children: [
+ {
+ name: 'id',
+ dataType: DataType.Varchar,
+ fullyQualifiedName: 'sample_kafka.customer_events.Customer.id',
+ tags: [],
+ },
+ {
+ name: 'first_name',
+ dataType: DataType.Varchar,
+ fullyQualifiedName: 'sample_kafka.customer_events.Customer.first_name',
+ tags: [],
+ },
+ {
+ name: 'last_name',
+ dataType: DataType.Varchar,
+ fullyQualifiedName: 'sample_kafka.customer_events.Customer.last_name',
+ tags: [],
+ },
+ {
+ name: 'email',
+ dataType: DataType.Varchar,
+ fullyQualifiedName: 'sample_kafka.customer_events.Customer.email',
+ tags: [],
+ },
+ ],
+ },
+ {
+ name: 'title2',
dataType: DataType.Varchar,
dataLength: 100,
dataTypeDisplay: 'varchar',
- description:
- 'Full name of the app or channel. For example, Point of Sale, Online Store.',
+ description: 'some description of title2',
fullyQualifiedName:
- 'sample_data.ecommerce_db.shopify."dim.api/client".title',
+ 'sample_data.ecommerce_db.shopify."dim.api/client".title2',
tags: [],
ordinalPosition: 2,
},
@@ -69,227 +239,103 @@ export const mockEntityDataWithoutNesting: Column[] = [
},
];
-export const mockEntityDataWithoutNestingResponse = [
+export const mockEntityDataWithNestingResponse: BasicEntityInfo[] = [
{
+ name: 'title2',
+ title: mockTextBasedSummaryTitleResponse,
+ type: DataType.Varchar,
+ description: mockListItemDescriptionHighlight,
+ tags: [],
+ tableConstraints: undefined,
+ columnConstraint: undefined,
children: [],
- description:
- 'ID of the API client that called the Shopify API. For example, the ID for the online store is 580111.',
+ },
+ {
name: 'api_client_id',
- tags: [
- {
- tagFQN: 'PersonalData.SpecialCategory',
- description:
- 'GDPR special category data is personal information of data subjects that is especially sensitive.',
- source: 'Classification',
- labelType: 'Manual',
- state: 'Confirmed',
- isHighlighted: true,
- },
- {
- tagFQN: 'PersonalData.Category1',
- description:
- 'GDPR special category data is personal information of data subjects that is especially sensitive.',
- source: 'Classification',
- labelType: 'Manual',
- state: 'Confirmed',
- },
- ],
title: (
api_client_id
),
- type: 'NUMERIC',
- tableConstraints: undefined,
- columnConstraint: undefined,
- },
- {
- children: [],
+ type: DataType.Numeric,
description:
- 'Full name of the app or channel. For example, Point of Sale, Online Store.',
- name: 'title',
- tags: [],
- title: (
-
- title
-
- ),
- type: 'VARCHAR',
+ 'ID of the API client that called the Shopify API. For example, the ID for the online store is 580111.',
+ tags: mockTagsSortAndHighlightResponse,
tableConstraints: undefined,
columnConstraint: undefined,
+ children: [],
},
-];
-
-export const mockEntityDataWithNesting: Field[] = [
{
name: 'Customer',
- dataType: DataTypeTopic.Record,
- fullyQualifiedName: 'sample_kafka.customer_events.Customer',
- tags: [],
- children: [
- {
- name: 'id',
- dataType: DataTypeTopic.String,
- fullyQualifiedName: 'sample_kafka.customer_events.Customer.id',
- tags: [],
- },
- {
- name: 'first_name',
- dataType: DataTypeTopic.String,
- fullyQualifiedName: 'sample_kafka.customer_events.Customer.first_name',
- tags: [],
- },
- {
- name: 'last_name',
- dataType: DataTypeTopic.String,
- fullyQualifiedName: 'sample_kafka.customer_events.Customer.last_name',
- tags: [],
- },
- {
- name: 'email',
- dataType: DataTypeTopic.String,
- fullyQualifiedName: 'sample_kafka.customer_events.Customer.email',
- tags: [],
- },
- {
- name: 'address_line_1',
- dataType: DataTypeTopic.String,
- fullyQualifiedName:
- 'sample_kafka.customer_events.Customer.address_line_1',
- tags: [],
- },
- {
- name: 'address_line_2',
- dataType: DataTypeTopic.String,
- fullyQualifiedName:
- 'sample_kafka.customer_events.Customer.address_line_2',
- tags: [],
- },
- {
- name: 'post_code',
- dataType: DataTypeTopic.String,
- fullyQualifiedName: 'sample_kafka.customer_events.Customer.post_code',
- tags: [],
- },
- {
- name: 'country',
- dataType: DataTypeTopic.String,
- fullyQualifiedName: 'sample_kafka.customer_events.Customer.country',
- tags: [],
- },
- ],
- },
-];
-
-export const mockEntityDataWithNestingResponse = [
- {
- children: [
- {
- children: [],
- description: undefined,
- name: 'id',
- tags: [],
- title: (
-
- id
-
- ),
- type: 'STRING',
- },
- {
- children: [],
- description: undefined,
- name: 'first_name',
- tags: [],
- title: (
-
- first_name
-
- ),
- type: 'STRING',
- },
- {
- children: [],
- description: undefined,
- name: 'last_name',
- tags: [],
- title: (
-
- last_name
-
- ),
- type: 'STRING',
- },
- {
- children: [],
- description: undefined,
- name: 'email',
- tags: [],
- title: (
-
- email
-
- ),
- type: 'STRING',
- },
- {
- children: [],
- description: undefined,
- name: 'address_line_1',
- tags: [],
- title: (
-
- address_line_1
-
- ),
- type: 'STRING',
- },
- {
- children: [],
- description: undefined,
- name: 'address_line_2',
- tags: [],
- title: (
-
- address_line_2
-
- ),
- type: 'STRING',
- },
- {
- children: [],
- description: undefined,
- name: 'post_code',
- tags: [],
- title: (
-
- post_code
-
- ),
- type: 'STRING',
- },
- {
- children: [],
- description: undefined,
- name: 'country',
- tags: [],
- title: (
-
- country
-
- ),
- type: 'STRING',
- },
- ],
- description: undefined,
- name: 'Customer',
- tags: [],
title: (
Customer
),
- type: 'RECORD',
+ type: DataType.Varchar,
+ tags: [],
+ description:
+ 'Full name of the app or channel. For example, Point of Sale, Online Store.',
+ tableConstraints: undefined,
+ columnConstraint: undefined,
+ children: [
+ {
+ name: 'id',
+ title: (
+
+ id
+
+ ),
+ type: DataType.Varchar,
+ tags: [],
+ children: [],
+ description: undefined,
+ tableConstraints: undefined,
+ columnConstraint: undefined,
+ },
+ {
+ name: 'first_name',
+ title: (
+
+ first_name
+
+ ),
+ type: DataType.Varchar,
+ tags: [],
+ children: [],
+ description: undefined,
+ tableConstraints: undefined,
+ columnConstraint: undefined,
+ },
+ {
+ name: 'last_name',
+ title: (
+
+ last_name
+
+ ),
+ type: DataType.Varchar,
+ tags: [],
+ children: [],
+ description: undefined,
+ tableConstraints: undefined,
+ columnConstraint: undefined,
+ },
+ {
+ name: 'email',
+ title: (
+
+ email
+
+ ),
+ type: DataType.Varchar,
+ tags: [],
+ children: [],
+ description: undefined,
+ tableConstraints: undefined,
+ columnConstraint: undefined,
+ },
+ ],
},
];
@@ -309,94 +355,3 @@ export const mockInvalidDataResponse = [
type: undefined,
},
];
-
-export const mockTagsDataBeforeSortAndHighlight = [
- {
- tagFQN: 'gs1.term1',
- name: 'term1',
- displayName: '',
- description: 'term1 desc',
- style: {},
- source: TagSource.Glossary,
- labelType: LabelType.Manual,
- state: State.Confirmed,
- },
- {
- tagFQN: 'gs1.term2',
- name: 'term2',
- displayName: '',
- description: 'term2 desc',
- style: {},
- source: TagSource.Glossary,
- labelType: LabelType.Manual,
- state: State.Confirmed,
- },
- {
- tagFQN: 'gs1.term3',
- name: 'term3',
- displayName: '',
- description: 'term3 desc',
- style: {},
- source: TagSource.Glossary,
- labelType: LabelType.Manual,
- state: State.Confirmed,
- },
-];
-
-export const mockTagsDataAfterSortAndHighlight = [
- {
- tagFQN: 'gs1.term2',
- name: 'term2',
- displayName: '',
- description: 'term2 desc',
- style: {},
- source: TagSource.Glossary,
- labelType: LabelType.Manual,
- state: State.Confirmed,
- isHighlighted: true,
- },
- {
- tagFQN: 'gs1.term1',
- name: 'term1',
- displayName: '',
- description: 'term1 desc',
- style: {},
- source: TagSource.Glossary,
- labelType: LabelType.Manual,
- state: State.Confirmed,
- },
- {
- tagFQN: 'gs1.term3',
- name: 'term3',
- displayName: '',
- description: 'term3 desc',
- style: {},
- source: TagSource.Glossary,
- labelType: LabelType.Manual,
- state: State.Confirmed,
- },
-];
-
-export const mockTagFQNsForHighlight = ['gs1.term2'];
-
-export const mockGetSummaryListItemTypeResponse = DataType.Varchar;
-
-export const mockTextBasedSummaryTitleResponse = (
-
- Title1
-
-);
-
-export const mockLinkBasedSummaryTitleResponse = (
-
-
-
- Title2
-
-
-
-
-);