Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: highlight search text functionality #19405

Merged
merged 9 commits into from
Jan 30, 2025
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,11 @@ import {
patchDatabaseSchemaDetails,
} from '../../../../rest/databaseAPI';
import { searchQuery } from '../../../../rest/searchAPI';
import { getEntityName } from '../../../../utils/EntityUtils';
import {
getEntityName,
highlightSearchText,
} from '../../../../utils/EntityUtils';
import { stringToHTML } from '../../../../utils/StringsUtils';
import { getUsagePercentile } from '../../../../utils/TableUtils';
import { showErrorToast } from '../../../../utils/ToastUtils';
import DisplayName from '../../../common/DisplayName/DisplayName';
Expand Down Expand Up @@ -221,7 +225,9 @@ export const DatabaseSchemaTable = ({
render: (_, record: DatabaseSchema) => (
<DisplayName
allowRename={allowEditDisplayNamePermission}
displayName={record.displayName}
displayName={stringToHTML(
highlightSearchText(record.displayName, searchValue)
)}
id={record.id ?? ''}
key={record.id}
link={
Expand All @@ -232,7 +238,7 @@ export const DatabaseSchemaTable = ({
)
: ''
}
name={record.name}
name={stringToHTML(highlightSearchText(record.name, searchValue))}
onEditDisplayName={handleDisplayNameUpdate}
/>
),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,5 @@ export interface ExploreSearchCardProps {
hideBreadcrumbs?: boolean;
actionPopoverContent?: React.ReactNode;
onCheckboxChange?: (checked: boolean) => void;
searchValue?: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { Table } from '../../../generated/entity/data/table';
import { EntityReference } from '../../../generated/entity/type';
import { TagLabel } from '../../../generated/tests/testCase';
import { AssetCertification } from '../../../generated/type/assetCertification';
import { getEntityName } from '../../../utils/EntityUtils';
import { getEntityName, highlightSearchText } from '../../../utils/EntityUtils';
import { getDomainPath } from '../../../utils/RouterUtils';
import searchClassBase from '../../../utils/SearchClassBase';
import { stringToHTML } from '../../../utils/StringsUtils';
Expand Down Expand Up @@ -61,6 +61,7 @@ const ExploreSearchCard: React.FC<ExploreSearchCardProps> = forwardRef<
showCheckboxes = false,
checked = false,
onCheckboxChange,
searchValue,
},
ref
) => {
Expand Down Expand Up @@ -222,7 +223,12 @@ const ExploreSearchCard: React.FC<ExploreSearchCardProps> = forwardRef<
<Typography.Text
className="text-lg font-medium text-link-color"
data-testid="entity-header-display-name">
{stringToHTML(searchClassBase.getEntityName(source))}
{stringToHTML(
highlightSearchText(
searchClassBase.getEntityName(source),
searchValue
)
)}
</Typography.Text>
</Button>
) : (
Expand Down Expand Up @@ -250,7 +256,12 @@ const ExploreSearchCard: React.FC<ExploreSearchCardProps> = forwardRef<
<Typography.Text
className="text-lg font-medium text-link-color break-word whitespace-normal"
data-testid="entity-header-display-name">
{stringToHTML(searchClassBase.getEntityName(source))}
{stringToHTML(
highlightSearchText(
searchClassBase.getEntityName(source),
searchValue
)
)}
</Typography.Text>
</Link>

Expand Down Expand Up @@ -296,7 +307,10 @@ const ExploreSearchCard: React.FC<ExploreSearchCardProps> = forwardRef<

<div className="p-t-sm">
<TableDataCardBody
description={source.description ?? ''}
description={highlightSearchText(
source.description ?? '',
searchValue
)}
extraInfo={otherDetails}
tags={showTags ? source.tags : []}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,7 @@ const AssetsTabs = forwardRef(
handleSummaryPanelDisplay={setSelectedCard}
id={_id}
key={'assets_' + _id}
searchValue={searchValue}
showCheckboxes={Boolean(activeEntity) && permissions.Create}
showTags={false}
source={_source}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,17 @@ jest.mock('../../../../hoc/LimitWrapper', () => {
return jest.fn().mockImplementation(() => <>LimitWrapper</>);
});

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),
getTitleCase: jest.fn((text) => text.charAt(0).toUpperCase() + text.slice(1)),
}));

describe('BotListV1', () => {
it('renders the component', () => {
render(<BotListV1 {...mockProps} />, { wrapper: MemoryRouter });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,12 @@ import LimitWrapper from '../../../../hoc/LimitWrapper';
import { useAuth } from '../../../../hooks/authHooks';
import { usePaging } from '../../../../hooks/paging/usePaging';
import { getBots } from '../../../../rest/botsAPI';
import { getEntityName } from '../../../../utils/EntityUtils';
import {
getEntityName,
highlightSearchText,
} from '../../../../utils/EntityUtils';
import { getSettingPageEntityBreadCrumb } from '../../../../utils/GlobalSettingsUtils';
import { stringToHTML } from '../../../../utils/StringsUtils';
import { showErrorToast } from '../../../../utils/ToastUtils';
import DeleteWidgetModal from '../../../common/DeleteWidget/DeleteWidgetModal';
import ErrorPlaceHolder from '../../../common/ErrorWithPlaceholder/ErrorPlaceHolder';
Expand Down Expand Up @@ -73,6 +77,7 @@ const BotListV1 = ({

const [handleErrorPlaceholder, setHandleErrorPlaceholder] = useState(false);
const [searchedData, setSearchedData] = useState<Bot[]>([]);
const [searchTerm, setSearchTerm] = useState<string>('');

const breadcrumbs: TitleBreadcrumbProps['titleLinks'] = useMemo(
() => getSettingPageEntityBreadCrumb(GlobalSettingsMenuCategory.BOTS),
Expand Down Expand Up @@ -123,7 +128,7 @@ const BotListV1 = ({

return (
<Link data-testid={`bot-link-${name}`} to={getBotsPath(fqn)}>
{name}
{stringToHTML(highlightSearchText(name, searchTerm))}
</Link>
);
},
Expand All @@ -134,7 +139,12 @@ const BotListV1 = ({
key: 'description',
render: (_, record) =>
record?.description ? (
<RichTextEditorPreviewerV1 markdown={record?.description || ''} />
<RichTextEditorPreviewerV1
markdown={highlightSearchText(
record?.description || '',
searchTerm
)}
/>
) : (
<span data-testid="no-description">
{t('label.no-entity', {
Expand Down Expand Up @@ -205,6 +215,7 @@ const BotListV1 = ({
}, [selectedUser]);

const handleSearch = (text: string) => {
setSearchTerm(text);
if (text) {
const normalizeText = lowerCase(text);
const matchedData = botUsers.filter(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ const Ingestion: React.FC<IngestionProps> = ({
isNumberBasedPaging={!isEmpty(searchText)}
pipelineIdToFetchStatus={pipelineIdToFetchStatus}
pipelineType={pipelineType}
searchText={searchText}
serviceCategory={serviceCategory}
serviceName={serviceName}
triggerIngestion={triggerIngestion}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,5 @@ export interface IngestionListTableProps {
record: IngestionPipeline
) => ReactNode;
tableClassName?: string;
searchText?: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,21 @@ jest.mock('../../../../common/NextPrevious/NextPrevious', () =>
);

jest.mock('../../../../../utils/IngestionListTableUtils', () => ({
renderNameField: jest.fn().mockImplementation(() => <div>nameField</div>),
renderNameField: jest
.fn()
.mockImplementation(() => () => <div>nameField</div>),
renderScheduleField: jest
.fn()
.mockImplementation(() => <div>scheduleField</div>),
renderStatusField: jest.fn().mockImplementation(() => <div>statusField</div>),
renderTypeField: jest.fn().mockImplementation(() => <div>typeField</div>),
renderTypeField: jest
.fn()
.mockImplementation(() => () => <div>typeField</div>),
}));

jest.mock('../../../../../utils/EntityUtils', () => ({
...jest.requireActual('../../../../../utils/EntityUtils'),
highlightSearchText: jest.fn((text) => text),
}));

describe('Ingestion', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,10 @@ import { UseAirflowStatusProps } from '../../../../../hooks/useAirflowStatus';
import { useApplicationStore } from '../../../../../hooks/useApplicationStore';
import { deleteIngestionPipelineById } from '../../../../../rest/ingestionPipelineAPI';
import { Transi18next } from '../../../../../utils/CommonUtils';
import { getEntityName } from '../../../../../utils/EntityUtils';
import {
getEntityName,
highlightSearchText,
} from '../../../../../utils/EntityUtils';
import {
renderNameField,
renderScheduleField,
Expand Down Expand Up @@ -81,6 +84,7 @@ function IngestionListTable({
triggerIngestion,
customRenderNameField,
tableClassName,
searchText,
}: Readonly<IngestionListTableProps>) {
const { t } = useTranslation();
const { theme } = useApplicationStore();
Expand Down Expand Up @@ -250,7 +254,7 @@ function IngestionListTable({
title: t('label.name'),
dataIndex: 'name',
key: 'name',
render: customRenderNameField ?? renderNameField,
render: customRenderNameField ?? renderNameField(searchText),
},
...(showDescriptionCol
? [
Expand All @@ -261,7 +265,7 @@ function IngestionListTable({
render: (description: string) =>
!isUndefined(description) && description.trim() ? (
<RichTextEditorPreviewerV1
markdown={description}
markdown={highlightSearchText(description, searchText)}
maxLength={MAX_CHAR_LIMIT_ENTITY_SUMMARY}
Ashish8689 marked this conversation as resolved.
Show resolved Hide resolved
/>
) : (
Expand All @@ -280,7 +284,7 @@ function IngestionListTable({
dataIndex: 'pipelineType',
key: 'pipelineType',
width: 120,
render: renderTypeField,
render: renderTypeField(searchText),
},
]),
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,10 +140,21 @@ jest.mock('../../../rest/serviceAPI', () => ({
searchService: mockSearchService,
}));

jest.mock('../../../utils/EntityUtils', () => ({
getEntityName: jest.fn().mockReturnValue('Glue'),
jest.mock('../../../utils/StringsUtils', () => ({
...jest.requireActual('../../../utils/StringsUtils'),
stringToHTML: jest.fn((text) => text),
}));

jest.mock('../../../utils/EntityUtils', () => {
const actual = jest.requireActual('../../../utils/EntityUtils');

return {
...actual,
getEntityName: jest.fn().mockReturnValue('Glue'),
highlightSearchText: jest.fn((text) => text),
};
});

jest.mock('../../../utils/PermissionsUtils', () => ({
checkPermission: jest.fn().mockReturnValue(true),
}));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,15 @@ import { DatabaseServiceSearchSource } from '../../../interface/search.interface
import { ServicesType } from '../../../interface/service.interface';
import { getServices, searchService } from '../../../rest/serviceAPI';
import { getServiceLogo } from '../../../utils/CommonUtils';
import { getEntityName } from '../../../utils/EntityUtils';
import { getEntityName, highlightSearchText } from '../../../utils/EntityUtils';
import { checkPermission } from '../../../utils/PermissionsUtils';
import { getAddServicePath } from '../../../utils/RouterUtils';
import {
getOptionalFields,
getResourceEntityFromServiceCategory,
getServiceTypesFromServiceCategory,
} from '../../../utils/ServiceUtils';
import { stringToHTML } from '../../../utils/StringsUtils';
import { showErrorToast } from '../../../utils/ToastUtils';
import ErrorPlaceHolder from '../../common/ErrorWithPlaceholder/ErrorPlaceHolder';
import { ListView } from '../../common/ListView/ListView.component';
Expand Down Expand Up @@ -314,7 +315,9 @@ const Services = ({ serviceName }: ServicesProps) => {
record.fullyQualifiedName ?? record.name,
serviceName
)}>
{getEntityName(record)}
{stringToHTML(
highlightSearchText(getEntityName(record), searchTerm)
)}
</Link>
</div>
),
Expand All @@ -329,7 +332,7 @@ const Services = ({ serviceName }: ServicesProps) => {
<RichTextEditorPreviewerV1
className="max-two-lines"
enableSeeMoreVariant={false}
markdown={description}
markdown={highlightSearchText(description, searchTerm)}
/>
Ashish8689 marked this conversation as resolved.
Show resolved Hide resolved
) : (
<span className="text-grey-muted">{t('label.no-description')}</span>
Expand All @@ -352,7 +355,9 @@ const Services = ({ serviceName }: ServicesProps) => {
filteredValue: serviceTypeFilter,
filters: serviceTypeFilters,
render: (serviceType) => (
<span className="font-normal text-grey-body">{serviceType}</span>
<span className="font-normal text-grey-body">
{stringToHTML(highlightSearchText(serviceType, searchTerm))}
</span>
),
},
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -707,6 +707,7 @@ const TeamDetailsV1 = ({
currentTeam={currentTeam}
data={childTeamList}
isFetchingAllTeamAdvancedDetails={isFetchingAllTeamAdvancedDetails}
searchTerm={searchTerm}
onTeamExpand={onTeamExpand}
/>
</Col>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,10 +51,18 @@ jest.mock('../../../../rest/teamsAPI', () => ({
.mockImplementation(() => Promise.resolve(MOCK_CURRENT_TEAM)),
}));

jest.mock('../../../../utils/EntityUtils', () => ({
getEntityName: jest.fn().mockReturnValue('entityName'),
jest.mock('../../../../utils/StringsUtils', () => ({
...jest.requireActual('../../../../utils/StringsUtils'),
stringToHTML: jest.fn((text) => text),
}));

jest.mock('../../../../utils/EntityUtils', () => {
return {
getEntityName: jest.fn().mockReturnValue('entityName'),
highlightSearchText: jest.fn((text) => text),
};
});

jest.mock('../../../../utils/RouterUtils', () => ({
getTeamsWithFqnPath: jest.fn().mockReturnValue([]),
}));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,12 @@ import { Team } from '../../../../generated/entity/teams/team';
import { Include } from '../../../../generated/type/include';
import { getTeamByName, patchTeamDetail } from '../../../../rest/teamsAPI';
import { Transi18next } from '../../../../utils/CommonUtils';
import { getEntityName } from '../../../../utils/EntityUtils';
import {
getEntityName,
highlightSearchText,
} from '../../../../utils/EntityUtils';
import { getTeamsWithFqnPath } from '../../../../utils/RouterUtils';
import { stringToHTML } from '../../../../utils/StringsUtils';
import { getTableExpandableConfig } from '../../../../utils/TableUtils';
import { isDropRestricted } from '../../../../utils/TeamUtils';
import { showErrorToast, showSuccessToast } from '../../../../utils/ToastUtils';
Expand All @@ -50,6 +54,7 @@ const TeamHierarchy: FC<TeamHierarchyProps> = ({
data,
onTeamExpand,
isFetchingAllTeamAdvancedDetails,
searchTerm,
}) => {
const { t } = useTranslation();
const [isModalOpen, setIsModalOpen] = useState<boolean>(false);
Expand All @@ -68,7 +73,9 @@ const TeamHierarchy: FC<TeamHierarchyProps> = ({
<Link
className="link-hover"
to={getTeamsWithFqnPath(record.fullyQualifiedName || record.name)}>
{getEntityName(record)}
{stringToHTML(
highlightSearchText(getEntityName(record), searchTerm)
)}
</Link>
),
},
Expand Down
Loading