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

perf: optionsToExclude array to loginsToExclude map #55348

Merged
merged 4 commits into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 49 additions & 44 deletions src/CONST.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,30 @@ type OnboardingMessage = {
const EMAIL_WITH_OPTIONAL_DOMAIN =
/(?=((?=[\w'#%+-]+(?:\.[\w'#%+-]+)*@?)[\w.'#%+-]{1,64}(?:@(?:(?=[a-z\d]+(?:-+[a-z\d]+)*\.)(?:[a-z\d-]{1,63}\.)+[a-z]{2,63}))?(?= |_|\b))(?<end>.*))\S{3,254}(?=\k<end>$)/;

const EMAIL = {
ACCOUNTING: '[email protected]',
ACCOUNTS_PAYABLE: '[email protected]',
ADMIN: '[email protected]',
BILLS: '[email protected]',
CHRONOS: '[email protected]',
CONCIERGE: '[email protected]',
CONTRIBUTORS: '[email protected]',
FIRST_RESPONDER: '[email protected]',
GUIDES_DOMAIN: 'team.expensify.com',
QA_DOMAIN: 'applause.expensifail.com',
HELP: '[email protected]',
INTEGRATION_TESTING_CREDS: '[email protected]',
NOTIFICATIONS: '[email protected]',
PAYROLL: '[email protected]',
QA: '[email protected]',
QA_TRAVIS: '[email protected]',
RECEIPTS: '[email protected]',
STUDENT_AMBASSADOR: '[email protected]',
SVFG: '[email protected]',
EXPENSIFY_EMAIL_DOMAIN: '@expensify.com',
EXPENSIFY_TEAM_EMAIL_DOMAIN: '@team.expensify.com',
};

const CONST = {
HEIC_SIGNATURES: [
'6674797068656963', // 'ftypheic' - Indicates standard HEIC file
Expand Down Expand Up @@ -1659,29 +1683,7 @@ const CONST = {
SEARCH_SKELETON_VIEW_ITEM_HEIGHT: 108,
EXPENSIFY_PARTNER_NAME: 'expensify.com',
EXPENSIFY_MERCHANT: 'Expensify, Inc.',
EMAIL: {
ACCOUNTING: '[email protected]',
ACCOUNTS_PAYABLE: '[email protected]',
ADMIN: '[email protected]',
BILLS: '[email protected]',
CHRONOS: '[email protected]',
CONCIERGE: '[email protected]',
CONTRIBUTORS: '[email protected]',
FIRST_RESPONDER: '[email protected]',
GUIDES_DOMAIN: 'team.expensify.com',
QA_DOMAIN: 'applause.expensifail.com',
HELP: '[email protected]',
INTEGRATION_TESTING_CREDS: '[email protected]',
NOTIFICATIONS: '[email protected]',
PAYROLL: '[email protected]',
QA: '[email protected]',
QA_TRAVIS: '[email protected]',
RECEIPTS: '[email protected]',
STUDENT_AMBASSADOR: '[email protected]',
SVFG: '[email protected]',
EXPENSIFY_EMAIL_DOMAIN: '@expensify.com',
EXPENSIFY_TEAM_EMAIL_DOMAIN: '@team.expensify.com',
},
EMAIL,

FULL_STORY: {
MASK: 'fs-mask',
Expand Down Expand Up @@ -3145,27 +3147,30 @@ const CONST = {
WORKSPACE_FEATURES: 'WorkspaceFeatures',
WORKSPACE_RULES: 'WorkspaceRules',
},
get EXPENSIFY_EMAILS() {
return [
this.EMAIL.ACCOUNTING,
this.EMAIL.ACCOUNTS_PAYABLE,
this.EMAIL.ADMIN,
this.EMAIL.BILLS,
this.EMAIL.CHRONOS,
this.EMAIL.CONCIERGE,
this.EMAIL.CONTRIBUTORS,
this.EMAIL.FIRST_RESPONDER,
this.EMAIL.HELP,
this.EMAIL.INTEGRATION_TESTING_CREDS,
this.EMAIL.NOTIFICATIONS,
this.EMAIL.PAYROLL,
this.EMAIL.QA,
this.EMAIL.QA_TRAVIS,
this.EMAIL.RECEIPTS,
this.EMAIL.STUDENT_AMBASSADOR,
this.EMAIL.SVFG,
];
},
EXPENSIFY_EMAILS_OBJECT: Object.entries(EMAIL).reduce((prev, [, email]) => {
// eslint-disable-next-line no-param-reassign
prev[email] = true;
return prev;
}, {} as Record<string, boolean>),
EXPENSIFY_EMAILS: [
EMAIL.ACCOUNTING,
EMAIL.ACCOUNTS_PAYABLE,
EMAIL.ADMIN,
EMAIL.BILLS,
EMAIL.CHRONOS,
EMAIL.CONCIERGE,
EMAIL.CONTRIBUTORS,
EMAIL.FIRST_RESPONDER,
EMAIL.HELP,
EMAIL.INTEGRATION_TESTING_CREDS,
EMAIL.NOTIFICATIONS,
EMAIL.PAYROLL,
EMAIL.QA,
EMAIL.QA_TRAVIS,
EMAIL.RECEIPTS,
EMAIL.STUDENT_AMBASSADOR,
EMAIL.SVFG,
] as string[],
get EXPENSIFY_ACCOUNT_IDS() {
return [
this.ACCOUNT_ID.ACCOUNTING,
Expand Down
2 changes: 1 addition & 1 deletion src/components/Search/SearchFiltersChatsSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@
import useDebouncedState from '@hooks/useDebouncedState';
import useLocalize from '@hooks/useLocalize';
import useScreenWrapperTransitionStatus from '@hooks/useScreenWrapperTransitionStatus';
import * as DeviceCapabilities from '@libs/DeviceCapabilities';

Check failure on line 11 in src/components/Search/SearchFiltersChatsSelector.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Namespace imports from @libs are not allowed. Use named imports instead. Example: import { method } from "@libs/module"
import * as OptionsListUtils from '@libs/OptionsListUtils';

Check failure on line 12 in src/components/Search/SearchFiltersChatsSelector.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Namespace imports from @libs are not allowed. Use named imports instead. Example: import { method } from "@libs/module"
import type {Option} from '@libs/OptionsListUtils';
import type {OptionData} from '@libs/ReportUtils';
import Navigation from '@navigation/Navigation';
import * as Report from '@userActions/Report';

Check failure on line 16 in src/components/Search/SearchFiltersChatsSelector.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Namespace imports from @userActions are not allowed. Use named imports instead. Example: import { action } from "@userActions/module"
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
Expand All @@ -27,7 +27,7 @@
};

function getSelectedOptionData(option: Option): OptionData {
return {...option, isSelected: true, reportID: option.reportID ?? '-1'};

Check failure on line 30 in src/components/Search/SearchFiltersChatsSelector.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

}

type SearchFiltersParticipantsSelectorProps = {
Expand Down Expand Up @@ -68,7 +68,7 @@
const chatOptions = useMemo(() => {
return OptionsListUtils.filterAndOrderOptions(defaultOptions, cleanSearchTerm, {
selectedOptions,
excludeLogins: CONST.EXPENSIFY_EMAILS,
excludeLogins: CONST.EXPENSIFY_EMAILS_OBJECT,
});
}, [defaultOptions, cleanSearchTerm, selectedOptions]);

Expand Down
4 changes: 2 additions & 2 deletions src/components/Search/SearchFiltersParticipantsSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@
import useDebouncedState from '@hooks/useDebouncedState';
import useLocalize from '@hooks/useLocalize';
import useScreenWrapperTranstionStatus from '@hooks/useScreenWrapperTransitionStatus';
import * as DeviceCapabilities from '@libs/DeviceCapabilities';

Check failure on line 11 in src/components/Search/SearchFiltersParticipantsSelector.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Namespace imports from @libs are not allowed. Use named imports instead. Example: import { method } from "@libs/module"
import * as OptionsListUtils from '@libs/OptionsListUtils';

Check failure on line 12 in src/components/Search/SearchFiltersParticipantsSelector.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Namespace imports from @libs are not allowed. Use named imports instead. Example: import { method } from "@libs/module"
import type {Option} from '@libs/OptionsListUtils';
import type {OptionData} from '@libs/ReportUtils';
import * as ReportUtils from '@libs/ReportUtils';

Check failure on line 15 in src/components/Search/SearchFiltersParticipantsSelector.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Namespace imports from @libs are not allowed. Use named imports instead. Example: import { method } from "@libs/module"
import Navigation from '@navigation/Navigation';
import * as Report from '@userActions/Report';

Check failure on line 17 in src/components/Search/SearchFiltersParticipantsSelector.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

Namespace imports from @userActions are not allowed. Use named imports instead. Example: import { action } from "@userActions/module"
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
Expand All @@ -28,7 +28,7 @@
};

function getSelectedOptionData(option: Option): OptionData {
return {...option, selected: true, reportID: option.reportID ?? '-1'};

Check failure on line 31 in src/components/Search/SearchFiltersParticipantsSelector.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

}

type SearchFiltersParticipantsSelectorProps = {
Expand Down Expand Up @@ -61,15 +61,15 @@
},
{
selectedOptions,
excludeLogins: CONST.EXPENSIFY_EMAILS,
excludeLogins: CONST.EXPENSIFY_EMAILS_OBJECT,
},
);
}, [areOptionsInitialized, options.personalDetails, options.reports, selectedOptions]);

const chatOptions = useMemo(() => {
return OptionsListUtils.filterAndOrderOptions(defaultOptions, cleanSearchTerm, {
selectedOptions,
excludeLogins: CONST.EXPENSIFY_EMAILS,
excludeLogins: CONST.EXPENSIFY_EMAILS_OBJECT,
maxRecentReportsToShow: CONST.IOU.MAX_RECENT_REPORTS_TO_SHOW,
});
}, [defaultOptions, cleanSearchTerm, selectedOptions]);
Expand Down
2 changes: 1 addition & 1 deletion src/components/Search/SearchRouter/SearchRouterList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,7 @@ function SearchRouterList(
personalDetails: options.personalDetails,
},
{
excludeLogins: CONST.EXPENSIFY_EMAILS,
excludeLogins: CONST.EXPENSIFY_EMAILS_OBJECT,
includeSelfDM: true,
showChatPreviewLine: true,
shouldBoldTitleByDefault: false,
Expand Down
75 changes: 46 additions & 29 deletions src/libs/OptionsListUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,11 +192,11 @@ type GetValidReportsConfig = {
includeMoneyRequests?: boolean;
includeInvoiceRooms?: boolean;
includeDomainEmail?: boolean;
optionsToExclude?: Array<Partial<OptionData>>;
loginsToExclude?: Record<string, boolean>;
} & GetValidOptionsSharedConfig;

type GetOptionsConfig = {
excludeLogins?: string[];
excludeLogins?: Record<string, boolean>;
includeRecentReports?: boolean;
includeSelectedOptions?: boolean;
recentAttendees?: Attendee[];
Expand All @@ -206,7 +206,7 @@ type GetOptionsConfig = {

type GetUserToInviteConfig = {
searchValue: string | undefined;
optionsToExclude?: Array<Partial<OptionData>>;
loginsToExclude?: Record<string, boolean>;
reportActions?: ReportActions;
shouldAcceptName?: boolean;
} & Pick<GetOptionsConfig, 'selectedOptions' | 'showChatPreviewLine'>;
Expand Down Expand Up @@ -240,7 +240,7 @@ type PreviewConfig = {showChatPreviewLine?: boolean; forcePolicyNamePreview?: bo

type FilterUserToInviteConfig = Pick<GetUserToInviteConfig, 'selectedOptions' | 'shouldAcceptName'> & {
canInviteUser?: boolean;
excludeLogins?: string[];
excludeLogins?: Record<string, boolean>;
};

type OrderOptionsConfig =
Expand Down Expand Up @@ -1189,7 +1189,7 @@ function canCreateOptimisticPersonalDetailOption({
*/
function getUserToInviteOption({
searchValue,
optionsToExclude = [],
loginsToExclude = {},
selectedOptions = [],
reportActions = {},
showChatPreviewLine = false,
Expand All @@ -1204,8 +1204,7 @@ function getUserToInviteOption({
const isInSelectedOption = selectedOptions.some((option) => 'login' in option && option.login === searchValue);
const isValidEmail = Str.isValidEmail(searchValue) && !Str.isDomainEmail(searchValue) && !Str.endsWith(searchValue, CONST.SMS.DOMAIN);
const isValidPhoneNumber = parsedPhoneNumber.possible && Str.isValidE164Phone(getPhoneNumberWithoutSpecialChars(parsedPhoneNumber.number?.input ?? ''));
const isInOptionToExclude =
optionsToExclude.findIndex((optionToExclude) => 'login' in optionToExclude && optionToExclude.login === addSMSDomainIfPhoneNumber(searchValue).toLowerCase()) !== -1;
const isInOptionToExclude = loginsToExclude[addSMSDomainIfPhoneNumber(searchValue).toLowerCase()];

if (isCurrentUserLogin || isInSelectedOption || (!isValidEmail && !isValidPhoneNumber && !shouldAcceptName) || isInOptionToExclude) {
return null;
Expand Down Expand Up @@ -1262,7 +1261,7 @@ function getValidReports(
includeP2P = true,
includeDomainEmail = false,
shouldBoldTitleByDefault = true,
optionsToExclude = [],
loginsToExclude = {},
}: GetValidReportsConfig,
) {
const topmostReportId = Navigation.getTopmostReportId();
Expand Down Expand Up @@ -1357,7 +1356,7 @@ function getValidReports(
}

// If we're excluding threads, check the report to see if it has a single participant and if the participant is already selected
if (!includeThreads && (!!option.login || option.reportID) && optionsToExclude.some((x) => x.login === option.login || x.reportID === option.reportID)) {
if (!includeThreads && ((!!option.login && loginsToExclude[option.login]) || loginsToExclude[option.reportID])) {
continue;
}

Expand Down Expand Up @@ -1411,7 +1410,7 @@ function getValidReports(
function getValidOptions(
options: OptionList,
{
excludeLogins = [],
excludeLogins = {},
includeSelectedOptions = false,
includeRecentReports = true,
recentAttendees,
Expand All @@ -1422,16 +1421,21 @@ function getValidOptions(
}: GetOptionsConfig = {},
): Options {
// Gather shared configs:
const optionsToExclude: Option[] = [{login: CONST.EMAIL.NOTIFICATIONS}];
const loginsToExclude: Record<string, boolean> = {
[CONST.EMAIL.NOTIFICATIONS]: true,
...excludeLogins,
};
// If we're including selected options from the search results, we only want to exclude them if the search input is empty
// This is because on certain pages, we show the selected options at the top when the search input is empty
// This prevents the issue of seeing the selected option twice if you have them as a recent chat and select them
if (!includeSelectedOptions) {
optionsToExclude.push(...selectedOptions);
selectedOptions.forEach((option) => {
if (!option.login) {
return;
}
loginsToExclude[option.login] = true;
});
}
excludeLogins.forEach((login) => {
optionsToExclude.push({login});
});
const {includeP2P = true, shouldBoldTitleByDefault = true, includeDomainEmail = false, ...getValidReportsConfig} = config;

// Get valid recent reports:
Expand All @@ -1442,19 +1446,33 @@ function getValidOptions(
includeP2P,
includeDomainEmail,
selectedOptions,
optionsToExclude,
loginsToExclude,
shouldBoldTitleByDefault,
});
} else if (recentAttendees && recentAttendees?.length > 0) {
recentAttendees.filter((attendee) => attendee.login ?? attendee.displayName).forEach((a) => optionsToExclude.push({login: a.login ?? a.displayName}));
recentAttendees.filter((attendee) => {
const login = attendee.login ?? attendee.displayName;
if (login) {
loginsToExclude[login] = true;
return true;
}

return false;
});
recentReportOptions = recentAttendees as OptionData[];
}

// Get valid personal details and check if we can find the current user:
const personalDetailsOptions: OptionData[] = [];
let currentUserOption: OptionData | undefined;
if (includeP2P) {
const personalDetailsOptionsToExclude = [...optionsToExclude, {login: currentUserLogin}];
let personalDetailLoginsToExclude = loginsToExclude;
if (currentUserLogin) {
personalDetailLoginsToExclude = {
...loginsToExclude,
[currentUserLogin]: true,
};
}
for (let i = 0; i < options.personalDetails.length; i++) {
// eslint-disable-next-line rulesdir/prefer-at
const detail = options.personalDetails[i];
Expand All @@ -1466,7 +1484,7 @@ function getValidOptions(
currentUserOption = detail;
}

if (personalDetailsOptionsToExclude.some((optionToExclude) => optionToExclude.login === detail.login)) {
if (personalDetailLoginsToExclude[detail.login]) {
continue;
}

Expand Down Expand Up @@ -1584,7 +1602,7 @@ function getAttendeeOptions(
{
betas,
selectedOptions: attendees,
excludeLogins: CONST.EXPENSIFY_EMAILS,
excludeLogins: CONST.EXPENSIFY_EMAILS_OBJECT,
includeOwnedWorkspaceChats,
includeRecentReports: false,
includeP2P,
Expand All @@ -1606,7 +1624,7 @@ function getShareDestinationOptions(
personalDetails: Array<SearchOption<PersonalDetails>> = [],
betas: OnyxEntry<Beta[]> = [],
selectedOptions: Array<Partial<OptionData>> = [],
excludeLogins: string[] = [],
excludeLogins: Record<string, boolean> = {},
includeOwnedWorkspaceChats = true,
) {
return getValidOptions(
Expand Down Expand Up @@ -1659,7 +1677,7 @@ function formatMemberForList(member: OptionData): MemberForList {
function getMemberInviteOptions(
personalDetails: Array<SearchOption<PersonalDetails>>,
betas: Beta[] = [],
excludeLogins: string[] = [],
excludeLogins: Record<string, boolean> = {},
includeSelectedOptions = false,
reports: Array<SearchOption<Report>> = [],
includeRecentReports = false,
Expand Down Expand Up @@ -1894,7 +1912,7 @@ function filterCurrentUserOption(currentUserOption: OptionData | null | undefine
}

function filterUserToInvite(options: Omit<Options, 'userToInvite'>, searchValue: string, config?: FilterUserToInviteConfig): OptionData | null {
const {canInviteUser = true, excludeLogins = []} = config ?? {};
const {canInviteUser = true, excludeLogins = {}} = config ?? {};
if (!canInviteUser) {
return null;
}
Expand All @@ -1910,14 +1928,13 @@ function filterUserToInvite(options: Omit<Options, 'userToInvite'>, searchValue:
return null;
}

const optionsToExclude: Option[] = [{login: CONST.EMAIL.NOTIFICATIONS}];
excludeLogins.forEach((login) => {
optionsToExclude.push({login});
});

const loginsToExclude: Record<string, boolean> = {
[CONST.EMAIL.NOTIFICATIONS]: true,
...excludeLogins,
};
return getUserToInviteOption({
searchValue,
optionsToExclude,
loginsToExclude,
...config,
});
}
Expand Down
Loading
Loading