Skip to content

Commit

Permalink
add useFastSearchFromOptions hook
Browse files Browse the repository at this point in the history
  • Loading branch information
hannojg committed Dec 13, 2024
1 parent 1f64b57 commit 928d330
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 48 deletions.
51 changes: 6 additions & 45 deletions src/components/Search/SearchRouter/SearchRouterList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ import type {SearchQueryItem, SearchQueryListItemProps} from '@components/Select
import type {SectionListDataType, SelectionListHandle, UserListItemProps} from '@components/SelectionList/types';
import UserListItem from '@components/SelectionList/UserListItem';
import useActiveWorkspace from '@hooks/useActiveWorkspace';
import useFastSearchFromOptions from '@hooks/useFastSearchFromOptions';
import useLocalize from '@hooks/useLocalize';
import usePolicy from '@hooks/usePolicy';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useThemeStyles from '@hooks/useThemeStyles';
import * as CardUtils from '@libs/CardUtils';
import FastSearch from '@libs/FastSearch';
import * as OptionsListUtils from '@libs/OptionsListUtils';
import type {SearchOption} from '@libs/OptionsListUtils';
import Performance from '@libs/Performance';
Expand Down Expand Up @@ -376,45 +376,7 @@ function SearchRouterList(
/**
* Builds a suffix tree and returns a function to search in it.
*/
const findInSearchTree = useMemo(() => {
const fastSearch = FastSearch.createFastSearch([
{
data: searchOptions.personalDetails,
toSearchableString: (option) => {
const displayName = option.participantsList?.[0]?.displayName ?? '';
return [option.login ?? '', option.login !== displayName ? displayName : ''].join();
},
},
{
data: searchOptions.recentReports,
toSearchableString: (option) => {
const searchStringForTree = [option.text ?? '', option.login ?? ''];

if (option.isThread) {
if (option.alternateText) {
searchStringForTree.push(option.alternateText);
}
} else if (!!option.isChatRoom || !!option.isPolicyExpenseChat) {
if (option.subtitle) {
searchStringForTree.push(option.subtitle);
}
}

return searchStringForTree.join();
},
},
]);
function search(searchInput: string) {
const [personalDetails, recentReports] = fastSearch.search(searchInput);

return {
personalDetails,
recentReports,
};
}

return search;
}, [searchOptions.personalDetails, searchOptions.recentReports]);
const filterOptions = useFastSearchFromOptions(searchOptions, {includeUserToInvite: true});

const recentReportsOptions = useMemo(() => {
if (autocompleteQueryValue.trim() === '') {
Expand All @@ -423,17 +385,16 @@ function SearchRouterList(

Timing.start(CONST.TIMING.SEARCH_FILTER_OPTIONS);
// const filteredOptions = OptionsListUtils.filterAndOrderOptions(searchOptions, autocompleteQueryValue, {sortByReportTypeInSearch: true, preferChatroomsOverThreads: true});
const filteredOptions = findInSearchTree(autocompleteQueryValue);
const filteredOptions = filterOptions(autocompleteQueryValue);
const orderedOptions = OptionsListUtils.orderOptions(filteredOptions, autocompleteQueryValue, {sortByReportTypeInSearch: true, preferChatroomsOverThreads: true});
const filteredUserToInvite = OptionsListUtils.filterUserToInvite(searchOptions, autocompleteQueryValue);
Timing.end(CONST.TIMING.SEARCH_FILTER_OPTIONS);

const reportOptions: OptionData[] = [...orderedOptions.recentReports, ...orderedOptions.personalDetails];
if (filteredUserToInvite) {
reportOptions.push(filteredUserToInvite);
if (filteredOptions.userToInvite) {
reportOptions.push(filteredOptions.userToInvite);
}
return reportOptions.slice(0, 20);
}, [autocompleteQueryValue, findInSearchTree, searchOptions]);
}, [autocompleteQueryValue, filterOptions, searchOptions]);

useEffect(() => {
ReportUserActions.searchInServer(autocompleteQueryValue.trim());
Expand Down
84 changes: 84 additions & 0 deletions src/hooks/useFastSearchFromOptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import {useMemo} from 'react';
import FastSearch from '@libs/FastSearch';
import * as OptionsListUtils from '@libs/OptionsListUtils';

type AllOrSelectiveOptions = OptionsListUtils.ReportAndPersonalDetailOptions | OptionsListUtils.Options;

type Options = {
includeUserToInvite: boolean;
};

// You can either use this to search within report and personal details options
function useFastSearchFromOptions(
options: OptionsListUtils.ReportAndPersonalDetailOptions,
config: {includeUserToInvite: false},
): (searchInput: string) => OptionsListUtils.ReportAndPersonalDetailOptions;
// Or you can use this to include the user invite option. This will require passing all options
function useFastSearchFromOptions(options: OptionsListUtils.Options, config: {includeUserToInvite: true}): (searchInput: string) => OptionsListUtils.Options;

/**
* Hook for making options from OptionsListUtils searchable with FastSearch.
*
* @example
* ```
* const options = OptionsListUtils.getSearchOptions(...);
* const filterOptions = useFastSearchFromOptions(options);
*/
function useFastSearchFromOptions(
options: OptionsListUtils.ReportAndPersonalDetailOptions | OptionsListUtils.Options,
{includeUserToInvite}: Options = {includeUserToInvite: false},
): (searchInput: string) => AllOrSelectiveOptions {
const findInSearchTree = useMemo(() => {
const fastSearch = FastSearch.createFastSearch([
{
data: options.personalDetails,
toSearchableString: (option) => {
const displayName = option.participantsList?.[0]?.displayName ?? '';
return [option.login ?? '', option.login !== displayName ? displayName : ''].join();
},
},
{
data: options.recentReports,
toSearchableString: (option) => {
const searchStringForTree = [option.text ?? '', option.login ?? ''];

if (option.isThread) {
if (option.alternateText) {
searchStringForTree.push(option.alternateText);
}
} else if (!!option.isChatRoom || !!option.isPolicyExpenseChat) {
if (option.subtitle) {
searchStringForTree.push(option.subtitle);
}
}

return searchStringForTree.join();
},
},
]);
function search(searchInput: string): AllOrSelectiveOptions {
const [personalDetails, recentReports] = fastSearch.search(searchInput);

if (includeUserToInvite && 'currentUserOption' in options) {
const userToInvite = OptionsListUtils.filterUserToInvite(options, searchInput);
return {
personalDetails,
recentReports,
userToInvite,
currentUserOption: options.currentUserOption,
};
}

return {
personalDetails,
recentReports,
};
}

return search;
}, [includeUserToInvite, options]);

return findInSearchTree;
}

export default useFastSearchFromOptions;
6 changes: 3 additions & 3 deletions src/libs/OptionsListUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,8 @@ type OrderOptionsConfig = {
preferRecentExpenseReports?: boolean;
};

type ReportAndPersonalDetailOptions = Pick<Options, 'recentReports' | 'personalDetails'>;

/**
* OptionsListUtils is used to build a list options passed to the OptionsList component. Several different UI views can
* be configured to display different results based on the options passed to the private getOptions() method. Public
Expand Down Expand Up @@ -993,8 +995,6 @@ function sortComparatorReportOptionByDate(options: ReportUtils.OptionData) {
return options.lastVisibleActionCreated ?? '';
}

type ReportAndPersonalDetailOptions = Pick<Options, 'recentReports' | 'personalDetails'>;

function orderOptions(options: ReportAndPersonalDetailOptions): ReportAndPersonalDetailOptions;
function orderOptions(options: ReportAndPersonalDetailOptions, searchValue: string, config?: OrderOptionsConfig): ReportAndPersonalDetailOptions;
function orderOptions(options: ReportAndPersonalDetailOptions, searchValue?: string, config?: OrderOptionsConfig) {
Expand Down Expand Up @@ -1840,4 +1840,4 @@ export {
hasReportErrors,
};

export type {Section, SectionBase, MemberForList, Options, OptionList, SearchOption, PayeePersonalDetails, Option, OptionTree};
export type {Section, SectionBase, MemberForList, Options, OptionList, SearchOption, PayeePersonalDetails, Option, OptionTree, ReportAndPersonalDetailOptions};

0 comments on commit 928d330

Please sign in to comment.