Skip to content

Commit

Permalink
Merge pull request #40235 from callstack-internal/perf/filter-money-r…
Browse files Browse the repository at this point in the history
…equest-participants

perf: Filter options in Request Money and Send Money
  • Loading branch information
roryabraham authored Jun 21, 2024
2 parents 2a9237e + cb671ac commit e0d9b9d
Show file tree
Hide file tree
Showing 7 changed files with 279 additions and 87 deletions.
1 change: 1 addition & 0 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1555,6 +1555,7 @@ const CONST = {
},

IOU: {
MAX_RECENT_REPORTS_TO_SHOW: 5,
// This is the transactionID used when going through the create expense flow so that it mimics a real transaction (like the edit flow)
OPTIMISTIC_TRANSACTION_ID: '1',
// Note: These payment types are used when building IOU reportAction message values in the server and should
Expand Down
124 changes: 92 additions & 32 deletions src/libs/OptionsListUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,10 @@ type Options = {

type PreviewConfig = {showChatPreviewLine?: boolean; forcePolicyNamePreview?: boolean; showPersonalDetails?: boolean};

type FilterOptionsConfig = Pick<GetOptionsConfig, 'betas'> & {preferChatroomsOverThreads: boolean};
type FilterOptionsConfig = Pick<
GetOptionsConfig,
'sortByReportTypeInSearch' | 'canInviteUser' | 'betas' | 'selectedOptions' | 'excludeUnknownUsers' | 'excludeLogins' | 'maxRecentReportsToShow'
> & {preferChatroomsOverThreads?: boolean};

/**
* OptionsListUtils is used to build a list options passed to the OptionsList component. Several different UI views can
Expand Down Expand Up @@ -1648,6 +1651,26 @@ function orderOptions(options: ReportUtils.OptionData[], searchValue: string | u
);
}

function canCreateOptimisticPersonalDetailOption({
searchValue,
recentReportOptions,
personalDetailsOptions,
currentUserOption,
}: {
searchValue: string;
recentReportOptions: ReportUtils.OptionData[];
personalDetailsOptions: ReportUtils.OptionData[];
currentUserOption?: ReportUtils.OptionData | null;
excludeUnknownUsers: boolean;
}) {
const noOptions = recentReportOptions.length + personalDetailsOptions.length === 0 && !currentUserOption;
const noOptionsMatchExactly = !personalDetailsOptions
.concat(recentReportOptions)
.find((option) => option.login === PhoneNumber.addSMSDomainIfPhoneNumber(searchValue ?? '').toLowerCase() || option.login === searchValue?.toLowerCase());

return noOptions || noOptionsMatchExactly;
}

/**
* We create a new user option if the following conditions are satisfied:
* - There's no matching recent report and personal detail option
Expand Down Expand Up @@ -2029,23 +2052,26 @@ function getOptions(
currentUserOption = undefined;
}

const noOptions = recentReportOptions.length + personalDetailsOptions.length === 0 && !currentUserOption;
const noOptionsMatchExactly = !personalDetailsOptions
.concat(recentReportOptions)
.find((option) => option.login === PhoneNumber.addSMSDomainIfPhoneNumber(searchValue ?? '').toLowerCase() || option.login === searchValue?.toLowerCase());

const userToInvite =
noOptions || noOptionsMatchExactly
? getUserToInviteOption({
searchValue,
excludeUnknownUsers,
optionsToExclude,
selectedOptions,
betas,
reportActions,
showChatPreviewLine,
})
: null;
let userToInvite: ReportUtils.OptionData | null = null;
if (
canCreateOptimisticPersonalDetailOption({
searchValue,
recentReportOptions,
personalDetailsOptions,
currentUserOption,
excludeUnknownUsers,
})
) {
userToInvite = getUserToInviteOption({
searchValue,
excludeUnknownUsers,
optionsToExclude,
selectedOptions,
betas,
reportActions,
showChatPreviewLine,
});
}

// If we are prioritizing 1:1 chats in search, do it only once we started searching
if (sortByReportTypeInSearch && searchValue !== '') {
Expand Down Expand Up @@ -2153,12 +2179,12 @@ function getFilteredOptions(
canInviteUser = true,
includeSelectedOptions = false,
includeTaxRates = false,
maxRecentReportsToShow: number = CONST.IOU.MAX_RECENT_REPORTS_TO_SHOW,
taxRates: TaxRatesWithDefault = {} as TaxRatesWithDefault,
includeSelfDM = false,
includePolicyReportFieldOptions = false,
policyReportFieldOptions: string[] = [],
recentlyUsedPolicyReportFieldOptions: string[] = [],
maxRecentReportsToShow = 5,
includeInvoiceRooms = false,
) {
return getOptions(
Expand Down Expand Up @@ -2395,14 +2421,25 @@ function getFirstKeyForList(data?: Option[] | null) {
* Filters options based on the search input value
*/
function filterOptions(options: Options, searchInputValue: string, config?: FilterOptionsConfig): Options {
const {betas = [], preferChatroomsOverThreads = false} = config ?? {};
const searchValue = getSearchValueForPhoneOrEmail(searchInputValue);
const {sortByReportTypeInSearch = false, canInviteUser = true, betas = [], maxRecentReportsToShow = 0, excludeLogins = [], preferChatroomsOverThreads = false} = config ?? {};
if (searchInputValue.trim() === '' && maxRecentReportsToShow > 0) {
return {...options, recentReports: options.recentReports.slice(0, maxRecentReportsToShow)};
}

const parsedPhoneNumber = PhoneNumber.parsePhoneNumber(LoginUtils.appendCountryCode(Str.removeSMSDomain(searchInputValue)));
const searchValue = parsedPhoneNumber.possible && parsedPhoneNumber.number?.e164 ? parsedPhoneNumber.number.e164 : searchInputValue.toLowerCase();
const searchTerms = searchValue ? searchValue.split(' ') : [];

// The regex below is used to remove dots only from the local part of the user email (local-part@domain)
// so that we can match emails that have dots without explicitly writing the dots (e.g: fistlast@domain will match first.last@domain)
const emailRegex = /\.(?=[^\s@]*@)/g;

const optionsToExclude: Option[] = [{login: CONST.EMAIL.NOTIFICATIONS}];

excludeLogins.forEach((login) => {
optionsToExclude.push({login});
});

const getParticipantsLoginsArray = (item: ReportUtils.OptionData) => {
const keys: string[] = [];
const visibleChatMemberAccountIDs = item.participantsList ?? [];
Expand Down Expand Up @@ -2435,16 +2472,23 @@ function filterOptions(options: Options, searchInputValue: string, config?: Filt
values.push(item.login.replace(emailRegex, ''));
}

if (!item.isChatRoom) {
const participantNames = getParticipantNames(item.participantsList ?? []);
values = values.concat(Array.from(participantNames));
}

if (item.isThread) {
if (item.alternateText) {
values.push(item.alternateText);
}
values = values.concat(getParticipantsLoginsArray(item));
} else if (!!item.isChatRoom || !!item.isPolicyExpenseChat) {
if (item.subtitle) {
values.push(item.subtitle);
}
} else {
values = values.concat(getParticipantsLoginsArray(item));
}
values = values.concat(getParticipantsLoginsArray(item));

return uniqFast(values);
});
Expand All @@ -2463,20 +2507,35 @@ function filterOptions(options: Options, searchInputValue: string, config?: Filt
};
}, options);

const recentReports = matchResults.recentReports.concat(matchResults.personalDetails);
const userToInvite =
recentReports.length === 0
? getUserToInviteOption({
searchValue,
betas,
})
: null;
let {recentReports, personalDetails} = matchResults;

if (sortByReportTypeInSearch) {
recentReports = recentReports.concat(personalDetails);
personalDetails = [];
recentReports = orderOptions(recentReports, searchValue);
}

let userToInvite = null;
if (canInviteUser) {
if (recentReports.length === 0) {
userToInvite = getUserToInviteOption({
searchValue,
betas,
selectedOptions: config?.selectedOptions,
optionsToExclude,
});
}
}

if (maxRecentReportsToShow > 0 && recentReports.length > maxRecentReportsToShow) {
recentReports.splice(maxRecentReportsToShow);
}

return {
personalDetails: [],
personalDetails,
recentReports: orderOptions(recentReports, searchValue, {preferChatroomsOverThreads}),
userToInvite,
currentUserOption: null,
currentUserOption: matchResults.currentUserOption,
categoryOptions: [],
tagOptions: [],
taxRatesOptions: [],
Expand Down Expand Up @@ -2520,6 +2579,7 @@ export {
getReportOption,
getTaxRatesSection,
getFirstKeyForList,
canCreateOptimisticPersonalDetailOption,
getUserToInviteOption,
};

Expand Down
2 changes: 1 addition & 1 deletion src/pages/ChatFinderPage/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ function ChatFinderPage({betas, isSearchingForReports, navigation}: ChatFinderPa
};
}

const newOptions = OptionsListUtils.filterOptions(searchOptions, debouncedSearchValue, {betas, preferChatroomsOverThreads: true});
const newOptions = OptionsListUtils.filterOptions(searchOptions, debouncedSearchValue, {sortByReportTypeInSearch: true, betas, preferChatroomsOverThreads: true});
const header = OptionsListUtils.getHeaderMessage(newOptions.recentReports.length + Number(!!newOptions.userToInvite) > 0, false, debouncedSearchValue);
return {
recentReports: newOptions.recentReports,
Expand Down
2 changes: 2 additions & 0 deletions src/pages/EditReportFieldDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import useDebouncedState from '@hooks/useDebouncedState';
import useLocalize from '@hooks/useLocalize';
import useTheme from '@hooks/useTheme';
import * as OptionsListUtils from '@libs/OptionsListUtils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {RecentlyUsedReportFields} from '@src/types/onyx';

Expand Down Expand Up @@ -86,6 +87,7 @@ function EditReportFieldDropdownPage({onSubmit, fieldKey, fieldValue, fieldOptio
false,
false,
undefined,
CONST.IOU.MAX_RECENT_REPORTS_TO_SHOW,
undefined,
undefined,
true,
Expand Down
1 change: 1 addition & 0 deletions src/pages/NewChatPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ function useOptions({isGroupChat}: NewChatPageProps) {
true,
undefined,
undefined,
CONST.IOU.MAX_RECENT_REPORTS_TO_SHOW,
undefined,
true,
);
Expand Down
Loading

0 comments on commit e0d9b9d

Please sign in to comment.