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

Move the logic of excluding contacts of DMs already included in reports to filterOptions #50426

18 changes: 6 additions & 12 deletions src/components/CategoryPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,20 +44,14 @@ function CategoryPicker({selectedCategory, policyID, onSubmit}: CategoryPickerPr
const [sections, headerMessage, shouldShowTextInput] = useMemo(() => {
const categories = policyCategories ?? policyCategoriesDraft ?? {};
const validPolicyRecentlyUsedCategories = policyRecentlyUsedCategories?.filter?.((p) => !isEmptyObject(p));
const {categoryOptions} = OptionsListUtils.getFilteredOptions(
[],
[],
[],
debouncedSearchValue,
const {categoryOptions} = OptionsListUtils.getFilteredOptions({
searchValue: debouncedSearchValue,
selectedOptions,
[],
false,
false,
true,
includeP2P: false,
includeCategories: true,
categories,
validPolicyRecentlyUsedCategories,
false,
);
recentlyUsedCategories: validPolicyRecentlyUsedCategories,
});

const categoryData = categoryOptions?.at(0)?.data ?? [];
const header = OptionsListUtils.getHeaderMessageForNonUserList(categoryData.length > 0, debouncedSearchValue);
Expand Down
27 changes: 6 additions & 21 deletions src/components/Search/SearchFiltersParticipantsSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,28 +57,13 @@ function SearchFiltersParticipantsSelector({initialAccountIDs, onFiltersUpdate}:
return defaultListOptions;
}

return OptionsListUtils.getFilteredOptions(
options.reports,
options.personalDetails,
undefined,
'',
return OptionsListUtils.getFilteredOptions({
reports: options.reports,
personalDetails: options.personalDetails,
selectedOptions,
CONST.EXPENSIFY_EMAILS,
false,
true,
false,
{},
[],
false,
{},
[],
true,
false,
false,
0,
undefined,
false,
);
excludeLogins: CONST.EXPENSIFY_EMAILS,
maxRecentReportsToShow: 0,
});
}, [areOptionsInitialized, options.personalDetails, options.reports, selectedOptions]);

const chatOptions = useMemo(() => {
Expand Down
39 changes: 17 additions & 22 deletions src/components/TagPicker/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React, {useMemo, useState} from 'react';
import type {OnyxEntry} from 'react-native-onyx';
import {withOnyx} from 'react-native-onyx';
import {useOnyx} from 'react-native-onyx';
import SelectionList from '@components/SelectionList';
import RadioListItem from '@components/SelectionList/RadioListItem';
import useLocalize from '@hooks/useLocalize';
Expand All @@ -10,7 +9,7 @@ import * as PolicyUtils from '@libs/PolicyUtils';
import type * as ReportUtils from '@libs/ReportUtils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {PolicyTag, PolicyTagLists, PolicyTags, RecentlyUsedTags} from '@src/types/onyx';
import type {PolicyTag, PolicyTags} from '@src/types/onyx';
import type {PendingAction} from '@src/types/onyx/OnyxCommon';

type SelectedTagOption = {
Expand All @@ -21,15 +20,7 @@ type SelectedTagOption = {
pendingAction?: PendingAction;
};

type TagPickerOnyxProps = {
/** Collection of tag list on a policy */
policyTags: OnyxEntry<PolicyTagLists>;

/** List of recently used tags */
policyRecentlyUsedTags: OnyxEntry<RecentlyUsedTags>;
};

type TagPickerProps = TagPickerOnyxProps & {
type TagPickerProps = {
/** The policyID we are getting tags for */
// It's used in withOnyx HOC.
// eslint-disable-next-line react/no-unused-prop-types
Expand All @@ -51,7 +42,9 @@ type TagPickerProps = TagPickerOnyxProps & {
tagListIndex: number;
};

function TagPicker({selectedTag, tagListName, policyTags, tagListIndex, policyRecentlyUsedTags, shouldShowDisabledAndSelectedOption = false, onSubmit}: TagPickerProps) {
function TagPicker({selectedTag, tagListName, policyID, tagListIndex, shouldShowDisabledAndSelectedOption = false, onSubmit}: TagPickerProps) {
const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`);
const [policyRecentlyUsedTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${policyID}`);
const styles = useThemeStyles();
const {translate} = useLocalize();
const [searchValue, setSearchValue] = useState('');
Expand Down Expand Up @@ -87,7 +80,16 @@ function TagPicker({selectedTag, tagListName, policyTags, tagListIndex, policyRe
}, [selectedOptions, policyTagList, shouldShowDisabledAndSelectedOption]);

const sections = useMemo(
() => OptionsListUtils.getFilteredOptions([], [], [], searchValue, selectedOptions, [], false, false, false, {}, [], true, enabledTags, policyRecentlyUsedTagsList, false).tagOptions,
() =>
OptionsListUtils.getFilteredOptions({
searchValue,
selectedOptions,
includeP2P: false,
includeTags: true,
tags: enabledTags,
recentlyUsedTags: policyRecentlyUsedTagsList,
canInviteUser: false,
}).tagOptions,
[searchValue, enabledTags, selectedOptions, policyRecentlyUsedTagsList],
);

Expand All @@ -113,13 +115,6 @@ function TagPicker({selectedTag, tagListName, policyTags, tagListIndex, policyRe

TagPicker.displayName = 'TagPicker';

export default withOnyx<TagPickerProps, TagPickerOnyxProps>({
policyTags: {
key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`,
},
policyRecentlyUsedTags: {
key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${policyID}`,
},
})(TagPicker);
export default TagPicker;

export type {SelectedTagOption};
112 changes: 76 additions & 36 deletions src/libs/OptionsListUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2073,11 +2073,6 @@ function getOptions(
}
}
}

// Add this login to the exclude list so it won't appear when we process the personal details
if (reportOption.login) {
optionsToExclude.push({login: reportOption.login});
}
}
}

Expand Down Expand Up @@ -2210,34 +2205,68 @@ function getIOUConfirmationOptionsFromPayeePersonalDetail(personalDetail: OnyxEn
/**
* Build the options for the New Group view
*/
function getFilteredOptions(
reports: Array<SearchOption<Report>> = [],
personalDetails: Array<SearchOption<PersonalDetails>> = [],
betas: OnyxEntry<Beta[]> = [],
searchValue = '',
selectedOptions: Array<Partial<ReportUtils.OptionData>> = [],
excludeLogins: string[] = [],
includeOwnedWorkspaceChats = false,
includeP2P = true,
includeCategories = false,
categories: PolicyCategories = {},
recentlyUsedCategories: string[] = [],
includeTags = false,
tags: PolicyTags | Array<PolicyTag | SelectedTagOption> = {},
recentlyUsedTags: string[] = [],
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[] = [],
includeInvoiceRooms = false,
action: IOUAction | undefined = undefined,
sortByReportTypeInSearch = false,
) {
type FilteredOptionsParams = {
reports?: Array<SearchOption<Report>>;
personalDetails?: Array<SearchOption<PersonalDetails>>;
betas?: OnyxEntry<Beta[]>;
searchValue?: string;
selectedOptions?: Array<Partial<ReportUtils.OptionData>>;
excludeLogins?: string[];
includeOwnedWorkspaceChats?: boolean;
includeP2P?: boolean;
includeCategories?: boolean;
categories?: PolicyCategories;
recentlyUsedCategories?: string[];
includeTags?: boolean;
tags?: PolicyTags | Array<PolicyTag | SelectedTagOption>;
recentlyUsedTags?: string[];
canInviteUser?: boolean;
includeSelectedOptions?: boolean;
includeTaxRates?: boolean;
taxRates?: TaxRatesWithDefault;
maxRecentReportsToShow?: number;
includeSelfDM?: boolean;
includePolicyReportFieldOptions?: boolean;
policyReportFieldOptions?: string[];
recentlyUsedPolicyReportFieldOptions?: string[];
includeInvoiceRooms?: boolean;
action?: IOUAction;
sortByReportTypeInSearch?: boolean;
};

type FilteredOptionsParamsWithDefaultSearchValue = Omit<FilteredOptionsParams, 'searchValue'> & {searchValue?: ''};

type FilteredOptionsParamsWithoutOptions = Omit<FilteredOptionsParams, 'reports' | 'personalDetails'> & {reports?: []; personalDetails?: []};

function getFilteredOptions(params: FilteredOptionsParamsWithDefaultSearchValue | FilteredOptionsParamsWithoutOptions) {
const {
reports = [],
personalDetails = [],
betas = [],
searchValue = '',
selectedOptions = [],
excludeLogins = [],
includeOwnedWorkspaceChats = false,
includeP2P = true,
includeCategories = false,
categories = {},
recentlyUsedCategories = [],
includeTags = false,
tags = {},
recentlyUsedTags = [],
canInviteUser = true,
includeSelectedOptions = false,
includeTaxRates = false,
maxRecentReportsToShow = CONST.IOU.MAX_RECENT_REPORTS_TO_SHOW,
taxRates = {} as TaxRatesWithDefault,
includeSelfDM = false,
includePolicyReportFieldOptions = false,
policyReportFieldOptions = [],
recentlyUsedPolicyReportFieldOptions = [],
includeInvoiceRooms = false,
action,
sortByReportTypeInSearch,
c3024 marked this conversation as resolved.
Show resolved Hide resolved
} = params;
return getOptions(
{reports, personalDetails},
{
Expand Down Expand Up @@ -2492,8 +2521,18 @@ function filterOptions(options: Options, searchInputValue: string, config?: Filt
preferPolicyExpenseChat = false,
preferRecentExpenseReports = false,
} = config ?? {};
function filteredPersonalDetailsOfRecentReports(recentReports: ReportUtils.OptionData[], personalDetails: ReportUtils.OptionData[]) {
const excludedLogins = new Set(recentReports.map((report) => report.login));
return personalDetails.filter((personalDetail) => !excludedLogins.has(personalDetail.login));
}
if (searchInputValue.trim() === '' && maxRecentReportsToShow > 0) {
return {...options, recentReports: options.recentReports.slice(0, maxRecentReportsToShow)};
const recentReports = options.recentReports.slice(0, maxRecentReportsToShow);
const personalDetails = filteredPersonalDetailsOfRecentReports(recentReports, options.personalDetails);
return {
...options,
recentReports,
personalDetails,
};
}

const parsedPhoneNumber = PhoneNumber.parsePhoneNumber(LoginUtils.appendCountryCode(Str.removeSMSDomain(searchInputValue)));
Expand Down Expand Up @@ -2535,7 +2574,6 @@ function filterOptions(options: Options, searchInputValue: string, config?: Filt
const currentUserOptionSearchText = items.currentUserOption ? uniqFast(getCurrentUserSearchTerms(items.currentUserOption)).join(' ') : '';

const currentUserOption = isSearchStringMatch(term, currentUserOptionSearchText) ? items.currentUserOption : null;

return {
recentReports: recentReports ?? [],
personalDetails: personalDetails ?? [],
Expand All @@ -2550,6 +2588,7 @@ function filterOptions(options: Options, searchInputValue: string, config?: Filt
let {recentReports, personalDetails} = matchResults;

if (sortByReportTypeInSearch) {
personalDetails = filteredPersonalDetailsOfRecentReports(recentReports, personalDetails);
recentReports = recentReports.concat(personalDetails);
personalDetails = [];
recentReports = orderOptions(recentReports, searchValue);
Expand All @@ -2569,9 +2608,10 @@ function filterOptions(options: Options, searchInputValue: string, config?: Filt
if (maxRecentReportsToShow > 0 && recentReports.length > maxRecentReportsToShow) {
recentReports.splice(maxRecentReportsToShow);
}
const filteredPersonalDetails = filteredPersonalDetailsOfRecentReports(recentReports, personalDetails);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a use case when maxRecentReportsToShow = 0 and recentReports is a long list? If so, then it seems there will be a performance downgrade here. Wdyt?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It will but that might not be significant.

We iterate over all recentReports here for each search word.

const recentReports = filterArrayByMatch(items.recentReports, term, (item) => {
const values: string[] = [];
if (item.text) {
values.push(item.text);
}
if (item.login) {
values.push(item.login);
values.push(item.login.replace(CONST.EMAIL_SEARCH_REGEX, ''));
}
if (item.isThread) {
if (item.alternateText) {
values.push(item.alternateText);
}
} else if (!!item.isChatRoom || !!item.isPolicyExpenseChat) {
if (item.subtitle) {
values.push(item.subtitle);
}
}
return uniqFast(values);
});

We iterate over the recentReports once more for this function filteredPersonalDetailsOfRecentReports.

Since, we pass searchTerm only to this function filterOptions and not getOptions, we need to do this exclusion of contacts only here.


return {
personalDetails,
personalDetails: filteredPersonalDetails,
recentReports: orderOptions(recentReports, searchValue, {preferChatroomsOverThreads, preferPolicyExpenseChat, preferRecentExpenseReports}),
userToInvite,
currentUserOption: matchResults.currentUserOption,
Expand Down
55 changes: 15 additions & 40 deletions src/pages/EditReportFieldDropdown.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import React, {useCallback, useMemo} from 'react';
import {withOnyx} from 'react-native-onyx';
import type {OnyxEntry} from 'react-native-onyx';
import {useOnyx} from 'react-native-onyx';
import Icon from '@components/Icon';
import * as Expensicons from '@components/Icon/Expensicons';
import SelectionList from '@components/SelectionList';
Expand All @@ -11,9 +10,7 @@ import useLocalize from '@hooks/useLocalize';
import useTheme from '@hooks/useTheme';
import localeCompare from '@libs/LocaleCompare';
import * as OptionsListUtils from '@libs/OptionsListUtils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {RecentlyUsedReportFields} from '@src/types/onyx';

type EditReportFieldDropdownPageComponentProps = {
/** Value of the policy report field */
Expand All @@ -33,13 +30,10 @@ type EditReportFieldDropdownPageComponentProps = {
onSubmit: (form: Record<string, string>) => void;
};

type EditReportFieldDropdownPageOnyxProps = {
recentlyUsedReportFields: OnyxEntry<RecentlyUsedReportFields>;
};

type EditReportFieldDropdownPageProps = EditReportFieldDropdownPageComponentProps & EditReportFieldDropdownPageOnyxProps;
type EditReportFieldDropdownPageProps = EditReportFieldDropdownPageComponentProps;

function EditReportFieldDropdownPage({onSubmit, fieldKey, fieldValue, fieldOptions, recentlyUsedReportFields}: EditReportFieldDropdownPageProps) {
function EditReportFieldDropdownPage({onSubmit, fieldKey, fieldValue, fieldOptions}: EditReportFieldDropdownPageProps) {
const [recentlyUsedReportFields] = useOnyx(ONYXKEYS.RECENTLY_USED_REPORT_FIELDS);
const [searchValue, debouncedSearchValue, setSearchValue] = useDebouncedState('');
const theme = useTheme();
const {translate} = useLocalize();
Expand All @@ -64,37 +58,22 @@ function EditReportFieldDropdownPage({onSubmit, fieldKey, fieldValue, fieldOptio
const [sections, headerMessage] = useMemo(() => {
const validFieldOptions = fieldOptions?.filter((option) => !!option)?.sort(localeCompare);

const {policyReportFieldOptions} = OptionsListUtils.getFilteredOptions(
[],
[],
[],
debouncedSearchValue,
[
const {policyReportFieldOptions} = OptionsListUtils.getFilteredOptions({
searchValue: debouncedSearchValue,
selectedOptions: [
{
keyForList: fieldValue,
searchText: fieldValue,
text: fieldValue,
},
],
[],
false,
false,
false,
{},
[],
false,
{},
[],
false,
false,
undefined,
CONST.IOU.MAX_RECENT_REPORTS_TO_SHOW,
undefined,
undefined,
true,
validFieldOptions,
recentlyUsedOptions,
);

includeP2P: false,
canInviteUser: false,
includePolicyReportFieldOptions: true,
policyReportFieldOptions: validFieldOptions,
recentlyUsedPolicyReportFieldOptions: recentlyUsedOptions,
});

const policyReportFieldData = policyReportFieldOptions?.[0]?.data ?? [];
const header = OptionsListUtils.getHeaderMessageForNonUserList(policyReportFieldData.length > 0, debouncedSearchValue);
Expand All @@ -121,8 +100,4 @@ function EditReportFieldDropdownPage({onSubmit, fieldKey, fieldValue, fieldOptio

EditReportFieldDropdownPage.displayName = 'EditReportFieldDropdownPage';

export default withOnyx<EditReportFieldDropdownPageProps, EditReportFieldDropdownPageOnyxProps>({
recentlyUsedReportFields: {
key: () => ONYXKEYS.RECENTLY_USED_REPORT_FIELDS,
},
})(EditReportFieldDropdownPage);
export default EditReportFieldDropdownPage;
Loading
Loading