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

[Mentions v2] Update auto-complete widget to perform online search when needed #40232

6 changes: 6 additions & 0 deletions src/libs/API/parameters/SearchForRoomsToMentionParams.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
type SearchForRoomsToMentionParams = {
searchInput: string;
policyID: string;
};

export default SearchForRoomsToMentionParams;
1 change: 1 addition & 0 deletions src/libs/API/parameters/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ export type {default as RequestAccountValidationLinkParams} from './RequestAccou
export type {default as ResolveActionableMentionWhisperParams} from './ResolveActionableMentionWhisperParams';
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 SendPerformanceTimingParams} from './SendPerformanceTimingParams';
export type {default as SetContactMethodAsDefaultParams} from './SetContactMethodAsDefaultParams';
export type {default as SignInUserWithLinkParams} from './SignInUserWithLinkParams';
Expand Down
2 changes: 2 additions & 0 deletions src/libs/API/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,7 @@ const READ_COMMANDS = {
GET_REPORT_PRIVATE_NOTE: 'GetReportPrivateNote',
OPEN_ROOM_MEMBERS_PAGE: 'OpenRoomMembersPage',
SEARCH_FOR_REPORTS: 'SearchForReports',
SEARCH_FOR_ROOMS_TO_MENTION: 'SearchForRoomsToMention',
SEND_PERFORMANCE_TIMING: 'SendPerformanceTiming',
GET_ROUTE: 'GetRoute',
GET_ROUTE_FOR_DRAFT: 'GetRouteForDraft',
Expand Down Expand Up @@ -484,6 +485,7 @@ type ReadCommandParameters = {
[READ_COMMANDS.GET_REPORT_PRIVATE_NOTE]: Parameters.GetReportPrivateNoteParams;
[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.SEND_PERFORMANCE_TIMING]: Parameters.SendPerformanceTimingParams;
[READ_COMMANDS.GET_ROUTE]: Parameters.GetRouteParams;
[READ_COMMANDS.GET_ROUTE_FOR_DRAFT]: Parameters.GetRouteParams;
Expand Down
15 changes: 10 additions & 5 deletions src/libs/actions/Report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import type {
RemoveFromRoomParams,
ResolveActionableMentionWhisperParams,
SearchForReportsParams,
SearchForRoomsToMentionParams,
SetNameValuePairParams,
TogglePinnedChatParams,
UpdateCommentParams,
Expand Down Expand Up @@ -3426,7 +3427,7 @@ function savePrivateNotesDraft(reportID: string, note: string) {
Onyx.merge(`${ONYXKEYS.COLLECTION.PRIVATE_NOTES_DRAFT}${reportID}`, note);
}

function searchForReports(searchInput: string) {
function searchForReports(searchInput: string, policyID?: string) {
// We do not try to make this request while offline because it sets a loading indicator optimistically
if (isNetworkOffline) {
Onyx.set(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, false);
Expand All @@ -3449,12 +3450,16 @@ function searchForReports(searchInput: string) {
},
];

const parameters: SearchForReportsParams = {searchInput};
const searchForRoomToMentionParams: SearchForRoomsToMentionParams = {searchInput, policyID: policyID ?? ''};
const searchForReportsParams: SearchForReportsParams = {searchInput};

API.read(READ_COMMANDS.SEARCH_FOR_REPORTS, parameters, {successData, failureData});
API.read(policyID ? READ_COMMANDS.SEARCH_FOR_ROOMS_TO_MENTION : READ_COMMANDS.SEARCH_FOR_REPORTS, policyID ? searchForRoomToMentionParams : searchForReportsParams, {
successData,
failureData,
});
}

function searchInServer(searchInput: string) {
function searchInServer(searchInput: string, policyID?: string) {
if (isNetworkOffline || !searchInput.trim().length) {
Onyx.set(ONYXKEYS.IS_SEARCHING_FOR_REPORTS, false);
return;
Expand All @@ -3464,7 +3469,7 @@ function searchInServer(searchInput: 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(searchInput);
searchForReports(searchInput, policyID);
}

function updateLastVisitTime(reportID: string) {
Expand Down
61 changes: 33 additions & 28 deletions src/pages/home/report/ReportActionCompose/SuggestionMention.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,24 @@
import Str from 'expensify-common/lib/str';
import lodashSortBy from 'lodash/sortBy';
import type {ForwardedRef, RefAttributes} from 'react';
import type {ForwardedRef} from 'react';
import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react';
import {withOnyx} from 'react-native-onyx';
import {useOnyx} from 'react-native-onyx';
import type {OnyxCollection} from 'react-native-onyx';
import * as Expensicons from '@components/Icon/Expensicons';
import type {Mention} from '@components/MentionSuggestions';
import MentionSuggestions from '@components/MentionSuggestions';
import {usePersonalDetails} from '@components/OnyxProvider';
import useArrowKeyFocusManager from '@hooks/useArrowKeyFocusManager';
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useDebounce from '@hooks/useDebounce';
import useLocalize from '@hooks/useLocalize';
import * as LoginUtils from '@libs/LoginUtils';
import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils';
import * as ReportUtils from '@libs/ReportUtils';
import * as SuggestionsUtils from '@libs/SuggestionUtils';
import * as UserUtils from '@libs/UserUtils';
import {isValidRoomName} from '@libs/ValidationUtils';
import * as ReportUserActions from '@userActions/Report';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {PersonalDetailsList, Report} from '@src/types/onyx';
Expand All @@ -31,11 +33,6 @@ type SuggestionValues = {
prefixType: string;
};

type RoomMentionOnyxProps = {
/** All reports shared with the user */
reports: OnyxCollection<Report>;
};

/**
* Check if this piece of string looks like a mention
*/
Expand All @@ -50,24 +47,15 @@ const defaultSuggestionsValues: SuggestionValues = {
};

function SuggestionMention(
{
value,
selection,
setSelection,
updateComment,
isAutoSuggestionPickerLarge,
measureParentContainer,
isComposerFocused,
reports,
isGroupPolicyReport,
policyID,
}: SuggestionProps & RoomMentionOnyxProps,
{value, selection, setSelection, updateComment, isAutoSuggestionPickerLarge, measureParentContainer, isComposerFocused, isGroupPolicyReport, policyID}: SuggestionProps,
ref: ForwardedRef<SuggestionsRef>,
) {
const personalDetails = usePersonalDetails() ?? CONST.EMPTY_OBJECT;
const {translate, formatPhoneNumber} = useLocalize();
const [suggestionValues, setSuggestionValues] = useState(defaultSuggestionsValues);

const [reports] = useOnyx(ONYXKEYS.COLLECTION.REPORT);

const currentUserPersonalDetails = useCurrentUserPersonalDetails();
const isMentionSuggestionsMenuVisible = !!suggestionValues.suggestedMentions.length && suggestionValues.shouldShowSuggestionMenu;

Expand All @@ -90,6 +78,21 @@ function SuggestionMention(
// Used to decide whether to block the suggestions list from showing to prevent flickering
const shouldBlockCalc = useRef(false);

/**
* Search for reports suggestions in server.
*
* The function is debounced to not perform requests on every keystroke.
*/
const debouncedSearchInServer = useDebounce(
useCallback(() => {
const foundSuggestionsCount = suggestionValues.suggestedMentions.length;
if (suggestionValues.prefixType === '#' && foundSuggestionsCount < 5) {
ReportUserActions.searchInServer(value, policyID);
}
}, [suggestionValues.suggestedMentions.length, suggestionValues.prefixType, policyID, value]),
CONST.TIMING.SEARCH_OPTION_LIST_DEBOUNCE_TIME,
);

const formatLoginPrivateDomain = useCallback(
(displayText = '', userLogin = '') => {
if (userLogin !== displayText) {
Expand Down Expand Up @@ -137,6 +140,7 @@ function SuggestionMention(
setSuggestionValues((prevState) => ({
...prevState,
suggestedMentions: [],
shouldShowSuggestionMenu: false,
}));
},
[
Expand Down Expand Up @@ -323,10 +327,11 @@ function SuggestionMention(

const shouldDisplayRoomMentionsSuggestions = isGroupPolicyReport && (isValidRoomName(suggestionWord.toLowerCase()) || prefix === '');
if (prefixType === '#' && shouldDisplayRoomMentionsSuggestions) {
// filter reports by room name and current policy
const filteredRoomMentions = getRoomMentionOptions(prefix, reports);
nextState.suggestedMentions = filteredRoomMentions;
nextState.shouldShowSuggestionMenu = !!filteredRoomMentions.length;
// Filter reports by room name and current policy
nextState.suggestedMentions = getRoomMentionOptions(prefix, reports);

// Even if there are no reports, we should show the suggestion menu - to perform live search
nextState.shouldShowSuggestionMenu = true;
war-in marked this conversation as resolved.
Show resolved Hide resolved
}

setSuggestionValues((prevState) => ({
Expand All @@ -342,6 +347,10 @@ function SuggestionMention(
calculateMentionSuggestion(selection.end);
}, [selection, calculateMentionSuggestion]);

useEffect(() => {
debouncedSearchInServer();
war-in marked this conversation as resolved.
Show resolved Hide resolved
}, [suggestionValues.suggestedMentions.length, suggestionValues.prefixType, policyID, value, debouncedSearchInServer]);

const updateShouldShowSuggestionMenuToFalse = useCallback(() => {
setSuggestionValues((prevState) => {
if (prevState.shouldShowSuggestionMenu) {
Expand Down Expand Up @@ -390,8 +399,4 @@ function SuggestionMention(

SuggestionMention.displayName = 'SuggestionMention';

export default withOnyx<SuggestionProps & RoomMentionOnyxProps & RefAttributes<SuggestionsRef>, RoomMentionOnyxProps>({
reports: {
key: ONYXKEYS.COLLECTION.REPORT,
},
})(forwardRef(SuggestionMention));
export default forwardRef(SuggestionMention);
Loading