mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-10-11 16:58:38 +00:00
feat(ui): highlight search term for schema table in table details page (#19110)
* feat: highlight search term * refactor: use stringToHTML instead of parse * test: highlightSearchText method * fix: test for schema table * fix: mock implemetation of stringToHTML * test: highlightSearchText function with null or falsy parameters
This commit is contained in:
parent
fec328bb98
commit
31b1132389
@ -55,9 +55,11 @@ import {
|
|||||||
getColumnSorter,
|
getColumnSorter,
|
||||||
getEntityName,
|
getEntityName,
|
||||||
getFrequentlyJoinedColumns,
|
getFrequentlyJoinedColumns,
|
||||||
|
highlightSearchText,
|
||||||
searchInColumns,
|
searchInColumns,
|
||||||
} from '../../../utils/EntityUtils';
|
} from '../../../utils/EntityUtils';
|
||||||
import { getEntityColumnFQN } from '../../../utils/FeedUtils';
|
import { getEntityColumnFQN } from '../../../utils/FeedUtils';
|
||||||
|
import { stringToHTML } from '../../../utils/StringsUtils';
|
||||||
import {
|
import {
|
||||||
getAllTags,
|
getAllTags,
|
||||||
searchTagInData,
|
searchTagInData,
|
||||||
@ -249,7 +251,7 @@ const SchemaTable = ({
|
|||||||
<Typography.Paragraph
|
<Typography.Paragraph
|
||||||
className="cursor-pointer"
|
className="cursor-pointer"
|
||||||
ellipsis={{ tooltip: displayValue, rows: 3 }}>
|
ellipsis={{ tooltip: displayValue, rows: 3 }}>
|
||||||
{displayValue}
|
{stringToHTML(highlightSearchText(displayValue, searchText))}
|
||||||
</Typography.Paragraph>
|
</Typography.Paragraph>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -264,7 +266,7 @@ const SchemaTable = ({
|
|||||||
<TableDescription
|
<TableDescription
|
||||||
columnData={{
|
columnData={{
|
||||||
fqn: record.fullyQualifiedName ?? '',
|
fqn: record.fullyQualifiedName ?? '',
|
||||||
field: record.description,
|
field: highlightSearchText(record.description, searchText),
|
||||||
record,
|
record,
|
||||||
}}
|
}}
|
||||||
entityFqn={tableFqn}
|
entityFqn={tableFqn}
|
||||||
@ -374,7 +376,7 @@ const SchemaTable = ({
|
|||||||
<Typography.Text
|
<Typography.Text
|
||||||
className="m-b-0 d-block text-grey-muted break-word"
|
className="m-b-0 d-block text-grey-muted break-word"
|
||||||
data-testid="column-name">
|
data-testid="column-name">
|
||||||
{name}
|
{stringToHTML(highlightSearchText(name, searchText))}
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
</div>
|
</div>
|
||||||
{!isEmpty(displayName) ? (
|
{!isEmpty(displayName) ? (
|
||||||
@ -382,7 +384,9 @@ const SchemaTable = ({
|
|||||||
<Typography.Text
|
<Typography.Text
|
||||||
className="m-b-0 d-block break-word"
|
className="m-b-0 d-block break-word"
|
||||||
data-testid="column-display-name">
|
data-testid="column-display-name">
|
||||||
{getEntityName(record)}
|
{stringToHTML(
|
||||||
|
highlightSearchText(getEntityName(record), searchText)
|
||||||
|
)}
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
|
@ -167,6 +167,16 @@ jest.mock('../../../constants/Table.constants', () => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
jest.mock('../../../utils/StringsUtils', () => ({
|
||||||
|
...jest.requireActual('../../../utils/StringsUtils'),
|
||||||
|
stringToHTML: jest.fn((text) => text),
|
||||||
|
}));
|
||||||
|
|
||||||
|
jest.mock('../../../utils/EntityUtils', () => ({
|
||||||
|
...jest.requireActual('../../../utils/EntityUtils'),
|
||||||
|
highlightSearchText: jest.fn((text) => text),
|
||||||
|
}));
|
||||||
|
|
||||||
describe('Test EntityTable Component', () => {
|
describe('Test EntityTable Component', () => {
|
||||||
it('Initially, Table should load', async () => {
|
it('Initially, Table should load', async () => {
|
||||||
render(<EntityTableV1 {...mockEntityTableProp} />, {
|
render(<EntityTableV1 {...mockEntityTableProp} />, {
|
||||||
|
@ -23,12 +23,16 @@ import {
|
|||||||
getEntityLinkFromType,
|
getEntityLinkFromType,
|
||||||
getEntityOverview,
|
getEntityOverview,
|
||||||
highlightEntityNameAndDescription,
|
highlightEntityNameAndDescription,
|
||||||
|
highlightSearchText,
|
||||||
} from './EntityUtils';
|
} from './EntityUtils';
|
||||||
import {
|
import {
|
||||||
entityWithoutNameAndDescHighlight,
|
entityWithoutNameAndDescHighlight,
|
||||||
highlightedEntityDescription,
|
highlightedEntityDescription,
|
||||||
highlightedEntityDisplayName,
|
highlightedEntityDisplayName,
|
||||||
|
mockHighlightedResult,
|
||||||
mockHighlights,
|
mockHighlights,
|
||||||
|
mockSearchText,
|
||||||
|
mockText,
|
||||||
} from './mocks/EntityUtils.mock';
|
} from './mocks/EntityUtils.mock';
|
||||||
|
|
||||||
jest.mock('../constants/constants', () => ({
|
jest.mock('../constants/constants', () => ({
|
||||||
@ -199,4 +203,56 @@ describe('EntityUtils unit tests', () => {
|
|||||||
expect(sorter(item2, item1)).toBe(1);
|
expect(sorter(item2, item1)).toBe(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('highlightSearchText method', () => {
|
||||||
|
it('should return the text with highlighted search text', () => {
|
||||||
|
const result = highlightSearchText(mockText, mockSearchText);
|
||||||
|
|
||||||
|
expect(result).toBe(mockHighlightedResult);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the original text if searchText is not found', () => {
|
||||||
|
const result = highlightSearchText(mockText, 'nonexistent');
|
||||||
|
|
||||||
|
expect(result).toBe(mockText);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return an empty string if no text is provided', () => {
|
||||||
|
const result = highlightSearchText('', 'test');
|
||||||
|
|
||||||
|
expect(result).toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return an empty string if no searchText is provided', () => {
|
||||||
|
const result = highlightSearchText(mockText, '');
|
||||||
|
|
||||||
|
expect(result).toBe(mockText);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return empty string if both text and searchText are missing', () => {
|
||||||
|
const result = highlightSearchText('', '');
|
||||||
|
|
||||||
|
expect(result).toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
|
const falsyTestCases = [
|
||||||
|
{ text: null, searchText: 'test', expected: '' },
|
||||||
|
{ text: 'mockText', searchText: null, expected: 'mockText' },
|
||||||
|
{ text: null, searchText: null, expected: '' },
|
||||||
|
{ text: 0 as any, searchText: '', expected: 0 },
|
||||||
|
{ text: false as any, searchText: '', expected: false },
|
||||||
|
];
|
||||||
|
|
||||||
|
it.each(falsyTestCases)(
|
||||||
|
'should return expected when text or searchText is null or falsy',
|
||||||
|
({ text, searchText, expected }) => {
|
||||||
|
const result = highlightSearchText(
|
||||||
|
text ?? undefined,
|
||||||
|
searchText ?? undefined
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(result).toBe(expected);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -2393,3 +2393,16 @@ export const getColumnSorter = <T, K extends keyof T>(field: K) => {
|
|||||||
return 0;
|
return 0;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const highlightSearchText = (
|
||||||
|
text?: string,
|
||||||
|
searchText?: string
|
||||||
|
): string => {
|
||||||
|
if (!searchText || !text) {
|
||||||
|
return text ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const regex = new RegExp(`(${searchText})`, 'gi');
|
||||||
|
|
||||||
|
return text.replace(regex, `<mark class="text-highlighter">$1</mark>`);
|
||||||
|
};
|
||||||
|
@ -136,3 +136,11 @@ export const mockHighlights = {
|
|||||||
export const highlightedEntityDescription = `This dimension table contains the billing and shipping <span class="text-highlighter">addresses</span> of customers.`;
|
export const highlightedEntityDescription = `This dimension table contains the billing and shipping <span class="text-highlighter">addresses</span> of customers.`;
|
||||||
|
|
||||||
export const highlightedEntityDisplayName = `dim_<span class="text-highlighter">address</span>`;
|
export const highlightedEntityDisplayName = `dim_<span class="text-highlighter">address</span>`;
|
||||||
|
|
||||||
|
export const mockText =
|
||||||
|
'This is a test description to verify highlightText method.';
|
||||||
|
|
||||||
|
export const mockSearchText = 'test';
|
||||||
|
|
||||||
|
export const mockHighlightedResult =
|
||||||
|
'This is a <mark class="text-highlighter">test</mark> description to verify highlightText method.';
|
||||||
|
Loading…
x
Reference in New Issue
Block a user