diff --git a/src/libs/API/parameters/SearchForUsersParams.ts b/src/libs/API/parameters/SearchForUsersParams.ts new file mode 100644 index 000000000000..6259cfafd86e --- /dev/null +++ b/src/libs/API/parameters/SearchForUsersParams.ts @@ -0,0 +1,6 @@ +type SearchForUsersParams = { + searchInput: string; + canCancel?: boolean; +}; + +export default SearchForUsersParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 8d6b01cc6317..e8a501c4b670 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -78,6 +78,7 @@ export type {default as ResolveActionableReportMentionWhisperParams} from './Res export type {default as RevealExpensifyCardDetailsParams} from './RevealExpensifyCardDetailsParams'; export type {default as SearchForReportsParams} from './SearchForReportsParams'; export type {default as SearchForRoomsToMentionParams} from './SearchForRoomsToMentionParams'; +export type {default as SearchForUsersParams} from './SearchForUsersParams'; export type {default as SendPerformanceTimingParams} from './SendPerformanceTimingParams'; export type {default as GraphiteParams} from './GraphiteParams'; export type {default as SetContactMethodAsDefaultParams} from './SetContactMethodAsDefaultParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 55420bcc09dc..007f349d140e 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -1113,6 +1113,7 @@ const READ_COMMANDS = { OPEN_ROOM_MEMBERS_PAGE: 'OpenRoomMembersPage', SEARCH_FOR_REPORTS: 'SearchForReports', SEARCH_FOR_ROOMS_TO_MENTION: 'SearchForRoomsToMention', + SEARCH_FOR_USERS: 'SearchForUsers', SEND_PERFORMANCE_TIMING: 'SendPerformanceTiming', GRAPHITE: 'Graphite', GET_ROUTE: 'GetRoute', @@ -1194,6 +1195,7 @@ type ReadCommandParameters = { [READ_COMMANDS.OPEN_ROOM_MEMBERS_PAGE]: Parameters.OpenRoomMembersPageParams; [READ_COMMANDS.SEARCH_FOR_REPORTS]: Parameters.SearchForReportsParams; [READ_COMMANDS.SEARCH_FOR_ROOMS_TO_MENTION]: Parameters.SearchForRoomsToMentionParams; + [READ_COMMANDS.SEARCH_FOR_USERS]: Parameters.SearchForUsersParams; [READ_COMMANDS.SEND_PERFORMANCE_TIMING]: Parameters.SendPerformanceTimingParams; [READ_COMMANDS.GRAPHITE]: Parameters.GraphiteParams; [READ_COMMANDS.GET_ROUTE]: Parameters.GetRouteParams; diff --git a/src/libs/HttpUtils.ts b/src/libs/HttpUtils.ts index b36d68ddbf12..6562fbc8cd68 100644 --- a/src/libs/HttpUtils.ts +++ b/src/libs/HttpUtils.ts @@ -18,6 +18,7 @@ let shouldForceOffline = false; const ABORT_COMMANDS = { All: 'All', [READ_COMMANDS.SEARCH_FOR_REPORTS]: READ_COMMANDS.SEARCH_FOR_REPORTS, + [READ_COMMANDS.SEARCH_FOR_USERS]: READ_COMMANDS.SEARCH_FOR_USERS, } as const; type AbortCommand = keyof typeof ABORT_COMMANDS; @@ -38,6 +39,7 @@ Onyx.connectWithoutView({ const abortControllerMap = new Map(); abortControllerMap.set(ABORT_COMMANDS.All, new AbortController()); abortControllerMap.set(ABORT_COMMANDS.SearchForReports, new AbortController()); +abortControllerMap.set(ABORT_COMMANDS.SearchForUsers, new AbortController()); /** * The API commands that require the skew calculation diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 5a45120de261..094f38e87e0c 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -43,6 +43,7 @@ import type { ResolveActionableReportMentionWhisperParams, SearchForReportsParams, SearchForRoomsToMentionParams, + SearchForUsersParams, TogglePinnedChatParams, TransactionThreadInfo, UpdateChatNameParams, @@ -4482,7 +4483,7 @@ function savePrivateNotesDraft(reportID: string, note: string) { Onyx.merge(`${ONYXKEYS.COLLECTION.PRIVATE_NOTES_DRAFT}${reportID}`, note); } -function searchForReports(isOffline: boolean, searchInput: string, policyID?: string) { +function searchForReports(isOffline: boolean, searchInput: string, policyID?: string, isUserSearch = false) { // We do not try to make this request while offline because it sets a loading indicator optimistically if (isOffline) { Onyx.set(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, false); @@ -4506,20 +4507,26 @@ function searchForReports(isOffline: boolean, searchInput: string, policyID?: st ]; const searchForRoomToMentionParams: SearchForRoomsToMentionParams = {query: searchInput.toLowerCase(), policyID}; - const searchForReportsParams: SearchForReportsParams = {searchInput: searchInput.toLowerCase(), canCancel: true}; + const searchForReportsOrUsersParams: SearchForReportsParams | SearchForUsersParams = {searchInput: searchInput.toLowerCase(), canCancel: true}; // We want to cancel all pending SearchForReports API calls before making another one if (!policyID) { - HttpUtils.cancelPendingRequests(READ_COMMANDS.SEARCH_FOR_REPORTS); + if (isUserSearch) { + HttpUtils.cancelPendingRequests(READ_COMMANDS.SEARCH_FOR_USERS); + } else { + HttpUtils.cancelPendingRequests(READ_COMMANDS.SEARCH_FOR_REPORTS); + } } - API.read(policyID ? READ_COMMANDS.SEARCH_FOR_ROOMS_TO_MENTION : READ_COMMANDS.SEARCH_FOR_REPORTS, policyID ? searchForRoomToMentionParams : searchForReportsParams, { + const searchForReportsOrUsersCommand = isUserSearch ? READ_COMMANDS.SEARCH_FOR_USERS : READ_COMMANDS.SEARCH_FOR_REPORTS; + + API.read(policyID ? READ_COMMANDS.SEARCH_FOR_ROOMS_TO_MENTION : searchForReportsOrUsersCommand, policyID ? searchForRoomToMentionParams : searchForReportsOrUsersParams, { successData, failureData, }); } -function searchInServer(searchInput: string, policyID?: string) { +function performServerSearch(searchInput: string, policyID?: string, isUserSearch = false) { // We are not getting isOffline from components as useEffect change will re-trigger the search on network change const isOffline = NetworkStore.isOffline(); if (isOffline || !searchInput.trim().length) { @@ -4531,7 +4538,15 @@ function searchInServer(searchInput: string, policyID?: string) { // we want to show the loading state right away. Otherwise, we will see a flashing UI where the client options are sorted and // tell the user there are no options, then we start searching, and tell them there are no options again. Onyx.set(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, true); - searchForReports(isOffline, searchInput, policyID); + searchForReports(isOffline, searchInput, policyID, isUserSearch); +} + +function searchInServer(searchInput: string, policyID?: string) { + performServerSearch(searchInput, policyID); +} + +function searchUserInServer(searchInput: string) { + performServerSearch(searchInput, undefined, true); } function updateLastVisitTime(reportID: string) { @@ -6360,6 +6375,7 @@ export { saveReportActionDraft, saveReportDraftComment, searchInServer, + searchUserInServer, setDeleteTransactionNavigateBackUrl, setGroupDraft, setIsComposerFullSize, diff --git a/src/pages/InviteReportParticipantsPage.tsx b/src/pages/InviteReportParticipantsPage.tsx index 92828c1fa0cc..715baf648c8d 100644 --- a/src/pages/InviteReportParticipantsPage.tsx +++ b/src/pages/InviteReportParticipantsPage.tsx @@ -14,7 +14,7 @@ import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import useSearchSelector from '@hooks/useSearchSelector'; import useThemeStyles from '@hooks/useThemeStyles'; -import {inviteToGroupChat, searchInServer} from '@libs/actions/Report'; +import {inviteToGroupChat, searchUserInServer} from '@libs/actions/Report'; import {clearUserSearchPhrase, updateUserSearchPhrase} from '@libs/actions/RoomMembersUserSearchPhrase'; import {canUseTouchScreen} from '@libs/DeviceCapabilities'; import {appendCountryCode} from '@libs/LoginUtils'; @@ -71,7 +71,7 @@ function InviteReportParticipantsPage({report}: InviteReportParticipantsPageProp useEffect(() => { updateUserSearchPhrase(debouncedSearchTerm); - searchInServer(debouncedSearchTerm); + searchUserInServer(debouncedSearchTerm); }, [debouncedSearchTerm]); const sections = useMemo(() => { diff --git a/src/pages/OnboardingWorkspaceInvite/BaseOnboardingWorkspaceInvite.tsx b/src/pages/OnboardingWorkspaceInvite/BaseOnboardingWorkspaceInvite.tsx index f247ef6fa75d..b81b5a5c01d2 100644 --- a/src/pages/OnboardingWorkspaceInvite/BaseOnboardingWorkspaceInvite.tsx +++ b/src/pages/OnboardingWorkspaceInvite/BaseOnboardingWorkspaceInvite.tsx @@ -20,7 +20,7 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useSearchSelector from '@hooks/useSearchSelector'; import useThemeStyles from '@hooks/useThemeStyles'; import {addMembersToWorkspace} from '@libs/actions/Policy/Member'; -import {searchInServer} from '@libs/actions/Report'; +import {searchUserInServer} from '@libs/actions/Report'; import {READ_COMMANDS} from '@libs/API/types'; import {canUseTouchScreen} from '@libs/DeviceCapabilities'; import HttpUtils from '@libs/HttpUtils'; @@ -86,7 +86,7 @@ function BaseOnboardingWorkspaceInvite({shouldUseNativeStyles}: BaseOnboardingWo const welcomeNote = useMemo(() => translate('workspace.common.welcomeNote'), [translate]); useEffect(() => { - searchInServer(debouncedSearchTerm); + searchUserInServer(debouncedSearchTerm); }, [debouncedSearchTerm]); const sections: Sections[] = useMemo(() => { diff --git a/src/pages/RoomInvitePage.tsx b/src/pages/RoomInvitePage.tsx index 570a97741823..dc2bbf938106 100644 --- a/src/pages/RoomInvitePage.tsx +++ b/src/pages/RoomInvitePage.tsx @@ -20,7 +20,7 @@ import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import useReportIsArchived from '@hooks/useReportIsArchived'; import useThemeStyles from '@hooks/useThemeStyles'; -import {inviteToRoomAction, searchInServer} from '@libs/actions/Report'; +import {inviteToRoomAction, searchUserInServer} from '@libs/actions/Report'; import {clearUserSearchPhrase, updateUserSearchPhrase} from '@libs/actions/RoomMembersUserSearchPhrase'; import {READ_COMMANDS} from '@libs/API/types'; import {canUseTouchScreen} from '@libs/DeviceCapabilities'; @@ -249,7 +249,7 @@ function RoomInvitePage({ useEffect(() => { updateUserSearchPhrase(debouncedSearchTerm); - searchInServer(debouncedSearchTerm); + searchUserInServer(debouncedSearchTerm); }, [debouncedSearchTerm]); let subtitleKey: '' | TranslationPaths | undefined; diff --git a/src/pages/iou/request/MoneyRequestAccountantSelector.tsx b/src/pages/iou/request/MoneyRequestAccountantSelector.tsx index cbcbe5739374..396561f0b90e 100644 --- a/src/pages/iou/request/MoneyRequestAccountantSelector.tsx +++ b/src/pages/iou/request/MoneyRequestAccountantSelector.tsx @@ -27,7 +27,7 @@ import { isCurrentUser, orderOptions, } from '@libs/OptionsListUtils'; -import {searchInServer} from '@userActions/Report'; +import {searchUserInServer} from '@userActions/Report'; import type {IOUAction, IOUType} from '@src/CONST'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -68,7 +68,7 @@ function MoneyRequestAccountantSelector({onFinish, onAccountantSelected, iouType const [loginList] = useOnyx(ONYXKEYS.LOGIN_LIST, {canBeMissing: true}); useEffect(() => { - searchInServer(debouncedSearchTerm.trim()); + searchUserInServer(debouncedSearchTerm.trim()); }, [debouncedSearchTerm]); const defaultOptions = useMemo(() => { diff --git a/src/pages/iou/request/MoneyRequestAttendeeSelector.tsx b/src/pages/iou/request/MoneyRequestAttendeeSelector.tsx index 36c8b99b1f8a..299bed4f18c9 100644 --- a/src/pages/iou/request/MoneyRequestAttendeeSelector.tsx +++ b/src/pages/iou/request/MoneyRequestAttendeeSelector.tsx @@ -17,7 +17,7 @@ import usePolicy from '@hooks/usePolicy'; import useScreenWrapperTransitionStatus from '@hooks/useScreenWrapperTransitionStatus'; import useSearchSelector from '@hooks/useSearchSelector'; import useThemeStyles from '@hooks/useThemeStyles'; -import {searchInServer} from '@libs/actions/Report'; +import {searchUserInServer} from '@libs/actions/Report'; import {canUseTouchScreen} from '@libs/DeviceCapabilities'; import { formatSectionsFromSearchTerm, @@ -121,7 +121,7 @@ function MoneyRequestAttendeeSelector({attendees = [], onFinish, onAttendeesAdde }); useEffect(() => { - searchInServer(debouncedSearchTerm.trim()); + searchUserInServer(debouncedSearchTerm.trim()); }, [debouncedSearchTerm]); const orderedAvailableOptions = useMemo(() => { diff --git a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx index 3e3d2cc472e4..a2e292632667 100644 --- a/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx +++ b/src/pages/iou/request/MoneyRequestParticipantsSelector.tsx @@ -41,7 +41,7 @@ import type {OptionData} from '@libs/ReportUtils'; import {isInvoiceRoom} from '@libs/ReportUtils'; import {shouldRestrictUserBillableActions} from '@libs/SubscriptionUtils'; import {getInvoicePrimaryWorkspace} from '@userActions/Policy/Policy'; -import {searchInServer} from '@userActions/Report'; +import {searchUserInServer} from '@userActions/Report'; import type {IOUAction, IOUType} from '@src/CONST'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -239,7 +239,7 @@ function MoneyRequestParticipantsSelector({ const cleanSearchTerm = useMemo(() => debouncedSearchTerm.trim().toLowerCase(), [debouncedSearchTerm]); useEffect(() => { - searchInServer(debouncedSearchTerm.trim()); + searchUserInServer(debouncedSearchTerm.trim()); }, [debouncedSearchTerm]); const inputHelperText = useMemo( diff --git a/src/pages/settings/Security/AddDelegate/AddDelegatePage.tsx b/src/pages/settings/Security/AddDelegate/AddDelegatePage.tsx index 727e69690c17..0949be642142 100644 --- a/src/pages/settings/Security/AddDelegate/AddDelegatePage.tsx +++ b/src/pages/settings/Security/AddDelegate/AddDelegatePage.tsx @@ -10,7 +10,7 @@ import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import useSearchSelector from '@hooks/useSearchSelector'; import useThemeStyles from '@hooks/useThemeStyles'; -import {searchInServer} from '@libs/actions/Report'; +import {searchUserInServer} from '@libs/actions/Report'; import Navigation from '@libs/Navigation/Navigation'; import {getHeaderMessage} from '@libs/OptionsListUtils'; import CONST from '@src/CONST'; @@ -95,7 +95,7 @@ function AddDelegatePage() { }, [availableOptions.recentReports, availableOptions.personalDetails, availableOptions.userToInvite, translate]); useEffect(() => { - searchInServer(debouncedSearchTerm); + searchUserInServer(debouncedSearchTerm); }, [debouncedSearchTerm]); return ( diff --git a/src/pages/tasks/TaskAssigneeSelectorModal.tsx b/src/pages/tasks/TaskAssigneeSelectorModal.tsx index 0bc735cd1dc7..dabcef027ee3 100644 --- a/src/pages/tasks/TaskAssigneeSelectorModal.tsx +++ b/src/pages/tasks/TaskAssigneeSelectorModal.tsx @@ -20,7 +20,7 @@ import useOnyx from '@hooks/useOnyx'; import useReportIsArchived from '@hooks/useReportIsArchived'; import useSearchSelector from '@hooks/useSearchSelector'; import useThemeStyles from '@hooks/useThemeStyles'; -import {searchInServer} from '@libs/actions/Report'; +import {searchUserInServer} from '@libs/actions/Report'; import {canModifyTask, editTaskAssignee, setAssigneeValue} from '@libs/actions/Task'; import {READ_COMMANDS} from '@libs/API/types'; import HttpUtils from '@libs/HttpUtils'; @@ -215,7 +215,7 @@ function TaskAssigneeSelectorModal() { const isTaskNonEditable = isTaskReport(report) && (!isTaskModifiable || !isOpen); useEffect(() => { - searchInServer(debouncedSearchTerm); + searchUserInServer(debouncedSearchTerm); }, [debouncedSearchTerm]); return ( diff --git a/src/pages/workspace/WorkspaceInvitePage.tsx b/src/pages/workspace/WorkspaceInvitePage.tsx index a14e976da4e7..495ca5c56c80 100644 --- a/src/pages/workspace/WorkspaceInvitePage.tsx +++ b/src/pages/workspace/WorkspaceInvitePage.tsx @@ -16,7 +16,7 @@ import useSearchSelector from '@hooks/useSearchSelector'; import useThemeStyles from '@hooks/useThemeStyles'; import {setWorkspaceInviteMembersDraft} from '@libs/actions/Policy/Member'; import {clearErrors, openWorkspaceInvitePage as policyOpenWorkspaceInvitePage, setWorkspaceErrors} from '@libs/actions/Policy/Policy'; -import {searchInServer} from '@libs/actions/Report'; +import {searchUserInServer} from '@libs/actions/Report'; import {READ_COMMANDS} from '@libs/API/types'; import {canUseTouchScreen} from '@libs/DeviceCapabilities'; import HttpUtils from '@libs/HttpUtils'; @@ -229,7 +229,7 @@ function WorkspaceInvitePage({route, policy}: WorkspaceInvitePageProps) { ); useEffect(() => { - searchInServer(debouncedSearchTerm); + searchUserInServer(debouncedSearchTerm); }, [debouncedSearchTerm]); return ( diff --git a/src/pages/workspace/companyCards/assignCard/AssigneeStep.tsx b/src/pages/workspace/companyCards/assignCard/AssigneeStep.tsx index 29f08344f134..018c9f9f5a15 100644 --- a/src/pages/workspace/companyCards/assignCard/AssigneeStep.tsx +++ b/src/pages/workspace/companyCards/assignCard/AssigneeStep.tsx @@ -15,7 +15,7 @@ import usePolicy from '@hooks/usePolicy'; import useSearchSelector from '@hooks/useSearchSelector'; import useThemeStyles from '@hooks/useThemeStyles'; import {setDraftInviteAccountID} from '@libs/actions/Card'; -import {searchInServer} from '@libs/actions/Report'; +import {searchUserInServer} from '@libs/actions/Report'; import {getDefaultCardName} from '@libs/CardUtils'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; @@ -222,7 +222,7 @@ function AssigneeStep({route}: AssigneeStepProps) { ]); useEffect(() => { - searchInServer(debouncedSearchTerm); + searchUserInServer(debouncedSearchTerm); }, [debouncedSearchTerm]); const headerMessage = useMemo(() => { diff --git a/src/pages/workspace/expensifyCard/issueNew/AssigneeStep.tsx b/src/pages/workspace/expensifyCard/issueNew/AssigneeStep.tsx index 8d2df39d9c84..3b31a4f82fbd 100644 --- a/src/pages/workspace/expensifyCard/issueNew/AssigneeStep.tsx +++ b/src/pages/workspace/expensifyCard/issueNew/AssigneeStep.tsx @@ -12,7 +12,7 @@ import useNetwork from '@hooks/useNetwork'; import useOnyx from '@hooks/useOnyx'; import useSearchSelector from '@hooks/useSearchSelector'; import useThemeStyles from '@hooks/useThemeStyles'; -import {searchInServer} from '@libs/actions/Report'; +import {searchUserInServer} from '@libs/actions/Report'; import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types'; import type {SettingsNavigatorParamList} from '@libs/Navigation/types'; import {getHeaderMessage, getSearchValueForPhoneOrEmail, sortAlphabetically} from '@libs/OptionsListUtils'; @@ -188,7 +188,7 @@ function AssigneeStep({policy, stepNames, startStepIndex, route}: AssigneeStepPr ]); useEffect(() => { - searchInServer(debouncedSearchTerm); + searchUserInServer(debouncedSearchTerm); }, [debouncedSearchTerm]); const headerMessage = useMemo(() => { diff --git a/tests/actions/ReportTest.ts b/tests/actions/ReportTest.ts index 7ed8cff409aa..c17131aa012c 100644 --- a/tests/actions/ReportTest.ts +++ b/tests/actions/ReportTest.ts @@ -2565,6 +2565,16 @@ describe('actions/Report', () => { }); }); + describe('searchUserInServer', () => { + it('should return the same result with or without uppercase input.', () => { + Report.searchUserInServer('test'); + Report.searchUserInServer('TEST'); + const upperCaseRequest = PersistedRequests.getAll().at(0); + const lowerCaseRequest = PersistedRequests.getAll().at(1); + expect(upperCaseRequest?.data?.searchInput).toBe(lowerCaseRequest?.data?.searchInput); + }); + }); + it('should not overwrite testDriveModalDismissed when it is already true', async () => { const TEST_USER_ACCOUNT_ID = 1; const TEST_USER_LOGIN = 'test@test.com';