From b74fa444e9fbdb57be159c656a42b198bb79283c Mon Sep 17 00:00:00 2001 From: Tomasz Palys Date: Thu, 19 Sep 2024 19:18:31 +0200 Subject: [PATCH] [lib] Exclude users that don't support thick threads from results Summary: When a user can't be added to thick thread, they shouldn't be included in search results. https://linear.app/comm/issue/ENG-9257/limit-chat-candidates-to-users-who-are-registered-with-identity Depends on D13404 Test Plan: Checked that the user who don't support thick threads aren't shown in add member modals of thick threads. Reviewers: kamil, will Reviewed By: kamil Subscribers: ashoat Differential Revision: https://phab.comm.dev/D13405 --- lib/shared/search-utils.js | 48 ++++++++++++++----- lib/shared/thread-actions-utils.js | 7 ++- lib/shared/thread-utils.js | 12 +++++ native/chat/compose-subchannel.react.js | 2 + native/chat/message-list-container.react.js | 2 + native/chat/settings/add-users-modal.react.js | 2 + web/chat/chat-thread-composer.react.js | 3 ++ web/settings/relationship/add-users-utils.js | 4 ++ 8 files changed, 63 insertions(+), 17 deletions(-) diff --git a/lib/shared/search-utils.js b/lib/shared/search-utils.js index 43784acbd0..2acc857009 100644 --- a/lib/shared/search-utils.js +++ b/lib/shared/search-utils.js @@ -4,7 +4,11 @@ import * as React from 'react'; import { messageID } from './message-utils.js'; import SearchIndex from './search-index.js'; -import { getContainingThreadID, userIsMember } from './thread-utils.js'; +import { + getContainingThreadID, + userIsMember, + userSupportsThickThreads, +} from './thread-utils.js'; import { searchMessagesActionTypes, useSearchMessages as useSearchMessagesAction, @@ -24,6 +28,7 @@ import type { } from '../selectors/chat-selectors.js'; import { useUserSearchIndex } from '../selectors/nav-selectors.js'; import { relationshipBlockedInEitherDirection } from '../shared/relationship-utils.js'; +import type { AuxUserInfos } from '../types/aux-user-types.js'; import type { MessageInfo, RawMessageInfo } from '../types/message-types.js'; import type { RoleInfo, @@ -36,6 +41,7 @@ import type { ThreadRolePermissionsBlob } from '../types/thread-permission-types import { type ThreadType, threadTypeIsSidebar, + threadTypeIsThick, } from '../types/thread-types-enum.js'; import type { AccountUserInfo, @@ -52,7 +58,7 @@ const notFriendNotice = 'not friend'; function appendUserInfo({ results, - excludeUserIDs, + shouldExcludeUserFromResult, userInfo, parentThreadInfo, communityThreadInfo, @@ -65,14 +71,14 @@ function appendUserInfo({ isMemberOfContainingThread: boolean, }, }, - +excludeUserIDs: $ReadOnlyArray, + +shouldExcludeUserFromResult: (userID: string) => boolean, +userInfo: AccountUserInfo | GlobalAccountUserInfo, +parentThreadInfo: ?ThreadInfo, +communityThreadInfo: ?ThreadInfo, +containingThreadInfo: ?ThreadInfo, }) { const { id } = userInfo; - if (excludeUserIDs.includes(id) || id in results) { + if (id in results || shouldExcludeUserFromResult(id)) { return; } @@ -104,6 +110,7 @@ function appendUserInfo({ function usePotentialMemberItems({ text, userInfos, + auxUserInfos, excludeUserIDs, includeServerSearchUsers, inputParentThreadInfo, @@ -112,6 +119,7 @@ function usePotentialMemberItems({ }: { +text: string, +userInfos: { +[id: string]: AccountUserInfo }, + +auxUserInfos: AuxUserInfos, +excludeUserIDs: $ReadOnlyArray, +includeServerSearchUsers?: $ReadOnlyArray, +inputParentThreadInfo?: ?ThreadInfo, @@ -149,6 +157,20 @@ function usePotentialMemberItems({ return null; }, [containingThreadID, communityThreadInfo, parentThreadInfo]); + const shouldExcludeUserFromResult = React.useCallback( + (userID: string) => { + if (excludeUserIDs.includes(userID)) { + return true; + } + return !!( + threadType && + threadTypeIsThick(threadType) && + !userSupportsThickThreads(userID, auxUserInfos) + ); + }, + [auxUserInfos, excludeUserIDs, threadType], + ); + const filteredUserResults = React.useMemo(() => { const results: { [id: string]: { @@ -161,7 +183,7 @@ function usePotentialMemberItems({ for (const id in userInfos) { appendUserInfo({ results, - excludeUserIDs, + shouldExcludeUserFromResult, userInfo: userInfos[id], parentThreadInfo, communityThreadInfo, @@ -173,7 +195,7 @@ function usePotentialMemberItems({ for (const id of ids) { appendUserInfo({ results, - excludeUserIDs, + shouldExcludeUserFromResult, userInfo: userInfos[id], parentThreadInfo, communityThreadInfo, @@ -186,7 +208,7 @@ function usePotentialMemberItems({ for (const userInfo of includeServerSearchUsers) { appendUserInfo({ results, - excludeUserIDs, + shouldExcludeUserFromResult, userInfo, parentThreadInfo, communityThreadInfo, @@ -214,14 +236,14 @@ function usePotentialMemberItems({ return userResults; }, [ - text, - userInfos, - searchIndex, - excludeUserIDs, + communityThreadInfo, + containingThreadInfo, includeServerSearchUsers, parentThreadInfo, - containingThreadInfo, - communityThreadInfo, + searchIndex, + shouldExcludeUserFromResult, + text, + userInfos, ]); const sortedMembers = React.useMemo(() => { diff --git a/lib/shared/thread-actions-utils.js b/lib/shared/thread-actions-utils.js index 622d78ad3c..f5e4c23c43 100644 --- a/lib/shared/thread-actions-utils.js +++ b/lib/shared/thread-actions-utils.js @@ -6,6 +6,7 @@ import { threadIsPending, threadOtherMembers, pendingThreadType, + userSupportsThickThreads, } from './thread-utils.js'; import { newThreadActionTypes, @@ -136,10 +137,8 @@ async function createRealThreadFromPendingThread({ otherMemberIDs.length > 0, 'otherMemberIDs should not be empty for threads', ); - const allUsersSupportThickThreads = otherMemberIDs.every( - memberID => - auxUserInfos[memberID]?.deviceList && - auxUserInfos[memberID].deviceList.devices.length > 0, + const allUsersSupportThickThreads = otherMemberIDs.every(memberID => + userSupportsThickThreads(memberID, auxUserInfos), ); if (threadTypeIsThick(threadInfo.type) && allUsersSupportThickThreads) { const type = assertThickThreadType( diff --git a/lib/shared/thread-utils.js b/lib/shared/thread-utils.js index c4148fe608..187c43d932 100644 --- a/lib/shared/thread-utils.js +++ b/lib/shared/thread-utils.js @@ -40,6 +40,7 @@ import { getRelativeMemberInfos, usersWithPersonalThreadSelector, } from '../selectors/user-selectors.js'; +import type { AuxUserInfos } from '../types/aux-user-types.js'; import type { RelativeMemberInfo, RawThreadInfo, @@ -1795,6 +1796,16 @@ function createThreadTimestamps( }; } +function userSupportsThickThreads( + userID: string, + auxUserInfos: AuxUserInfos, +): boolean { + return ( + !!auxUserInfos[userID]?.deviceList && + auxUserInfos[userID].deviceList.devices.length > 0 + ); +} + export { threadHasPermission, useCommunityRootMembersToRole, @@ -1861,4 +1872,5 @@ export { extractMentionedMembers, isMemberActive, createThreadTimestamps, + userSupportsThickThreads, }; diff --git a/native/chat/compose-subchannel.react.js b/native/chat/compose-subchannel.react.js index 9266151748..38f430a29a 100644 --- a/native/chat/compose-subchannel.react.js +++ b/native/chat/compose-subchannel.react.js @@ -196,9 +196,11 @@ function ComposeSubchannel(props: Props): React.Node { const communityThreadInfo = useSelector(state => community ? threadInfoSelector(state)[community] : null, ); + const auxUserInfos = useSelector(state => state.auxUserStore.auxUserInfos); const userSearchResults = usePotentialMemberItems({ text: usernameInputText, userInfos: otherUserInfos, + auxUserInfos, excludeUserIDs: userInfoInputIDs, inputParentThreadInfo: parentThreadInfo, inputCommunityThreadInfo: communityThreadInfo, diff --git a/native/chat/message-list-container.react.js b/native/chat/message-list-container.react.js index 7b69070a5e..99a0884e2c 100644 --- a/native/chat/message-list-container.react.js +++ b/native/chat/message-list-container.react.js @@ -267,9 +267,11 @@ const ConnectedMessageListContainer: React.ComponentType = const serverSearchResults = useSearchUsers(usernameInputText); + const auxUserInfos = useSelector(state => state.auxUserStore.auxUserInfos); const userSearchResults = usePotentialMemberItems({ text: usernameInputText, userInfos: otherUserInfos, + auxUserInfos, excludeUserIDs: userInfoInputArray.map(userInfo => userInfo.id), includeServerSearchUsers: serverSearchResults, }); diff --git a/native/chat/settings/add-users-modal.react.js b/native/chat/settings/add-users-modal.react.js index 008d2a0ac9..36fc72f909 100644 --- a/native/chat/settings/add-users-modal.react.js +++ b/native/chat/settings/add-users-modal.react.js @@ -187,9 +187,11 @@ function AddUsersModal(props: Props): React.Node { const communityThreadInfo = useSelector(state => community ? threadInfoSelector(state)[community] : null, ); + const auxUserInfos = useSelector(state => state.auxUserStore.auxUserInfos); const userSearchResults = usePotentialMemberItems({ text: usernameInputText, userInfos: otherUserInfos, + auxUserInfos, excludeUserIDs, inputParentThreadInfo: parentThreadInfo, inputCommunityThreadInfo: communityThreadInfo, diff --git a/web/chat/chat-thread-composer.react.js b/web/chat/chat-thread-composer.react.js index aee35ce4c0..c15d8d1e87 100644 --- a/web/chat/chat-thread-composer.react.js +++ b/web/chat/chat-thread-composer.react.js @@ -31,6 +31,7 @@ import Search from '../components/search.react.js'; import type { InputState } from '../input/input-state.js'; import Alert from '../modals/alert.react.js'; import { updateNavInfoActionType } from '../redux/action-types.js'; +import { useSelector } from '../redux/redux-utils.js'; type Props = { +userInfoInputArray: $ReadOnlyArray, @@ -57,9 +58,11 @@ function ChatThreadComposer(props: Props): React.Node { const searchResults = useSearchUsers(usernameInputText); + const auxUserInfos = useSelector(state => state.auxUserStore.auxUserInfos); const userListItems = usePotentialMemberItems({ text: usernameInputText, userInfos: otherUserInfos, + auxUserInfos, excludeUserIDs: userInfoInputIDs, includeServerSearchUsers: searchResults, }); diff --git a/web/settings/relationship/add-users-utils.js b/web/settings/relationship/add-users-utils.js index 01774c04b4..c3c80b5f64 100644 --- a/web/settings/relationship/add-users-utils.js +++ b/web/settings/relationship/add-users-utils.js @@ -147,9 +147,11 @@ function useAddMembersListUserInfos(params: UseAddMembersListUserInfosParams): { [previouslySelectedUsers, threadInfo.members], ); + const auxUserInfos = useSelector(state => state.auxUserStore.auxUserInfos); const userSearchResults = usePotentialMemberItems({ text: searchText, userInfos: otherUserInfos, + auxUserInfos, excludeUserIDs, inputParentThreadInfo: parentThreadInfo, inputCommunityThreadInfo: communityThreadInfo, @@ -213,9 +215,11 @@ function useSubchannelAddMembersListUserInfos( [previouslySelectedUsers], ); + const auxUserInfos = useSelector(state => state.auxUserStore.auxUserInfos); const userSearchResults = usePotentialMemberItems({ text: searchText, userInfos: otherUserInfos, + auxUserInfos, excludeUserIDs: previouslySelectedUserIDs, inputParentThreadInfo: parentThreadInfo, inputCommunityThreadInfo: communityThreadInfo,