Skip to content

Fix - Incorrect member count in report details after commenting or inviting another member #47207

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

Merged
merged 6 commits into from
Aug 14, 2024
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
33 changes: 33 additions & 0 deletions src/libs/ReportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ import isReportMessageAttachment from './isReportMessageAttachment';
import localeCompare from './LocaleCompare';
import * as LocalePhoneNumber from './LocalePhoneNumber';
import * as Localize from './Localize';
import Log from './Log';
import {isEmailPublicDomain} from './LoginUtils';
import ModifiedExpenseMessage from './ModifiedExpenseMessage';
import linkingConfig from './Navigation/linkingConfig';
Expand Down Expand Up @@ -2033,6 +2034,37 @@ function getParticipantsAccountIDsForDisplay(report: OnyxEntry<Report>, shouldEx
return participantsEntries.map(([accountID]) => Number(accountID));
}

function getParticipantsList(report: Report, personalDetails: OnyxEntry<PersonalDetailsList>, isRoomMembersList = false): number[] {
const isReportGroupChat = isGroupChat(report);
const isReportIOU = isIOUReport(report);
const shouldExcludeHiddenParticipants = !isReportGroupChat && !isReportIOU;
const chatParticipants = getParticipantsAccountIDsForDisplay(report, isRoomMembersList || shouldExcludeHiddenParticipants);

return chatParticipants.filter((accountID) => {
const details = personalDetails?.[accountID];

if (!isRoomMembersList) {
if (!details) {
Log.hmmm(`[ReportParticipantsPage] no personal details found for Group chat member with accountID: ${accountID}`);
return false;
}
} else {
// When adding a new member to a room (whose personal detail does not exist in Onyx), an optimistic personal detail
// is created. However, when the real personal detail is returned from the backend, a duplicate member may appear
// briefly before the optimistic personal detail is deleted. To address this, we filter out the optimistically created
// member here.
const isDuplicateOptimisticDetail =
details?.isOptimisticPersonalDetail && chatParticipants.some((accID) => accID !== accountID && details.login === personalDetails?.[accID]?.login);

if (!details || isDuplicateOptimisticDetail) {
Log.hmmm(`[RoomMembersPage] no personal details found for room member with accountID: ${accountID}`);
return false;
}
}
return true;
});
}

function buildParticipantsFromAccountIDs(accountIDs: number[]): Participants {
const finalParticipants: Participants = {};
return accountIDs.reduce((participants, accountID) => {
Expand Down Expand Up @@ -7638,6 +7670,7 @@ export {
getParentNavigationSubtitle,
getParsedComment,
getParticipantsAccountIDsForDisplay,
getParticipantsList,
getParticipants,
getPendingChatMembers,
getPersonalDetailsForAccountID,
Expand Down
9 changes: 5 additions & 4 deletions src/pages/ReportDetailsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -136,10 +136,10 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD
const isGroupChat = useMemo(() => ReportUtils.isGroupChat(report), [report]);
const isRootGroupChat = useMemo(() => ReportUtils.isRootGroupChat(report), [report]);
const isThread = useMemo(() => ReportUtils.isThread(report), [report]);
const shouldOpenRoomMembersPage = isUserCreatedPolicyRoom || isChatThread || (isPolicyExpenseChat && isPolicyAdmin);
const participants = useMemo(() => {
const shouldExcludeHiddenParticipants = !isGroupChat && !isSystemChat;
return ReportUtils.getParticipantsAccountIDsForDisplay(report, shouldExcludeHiddenParticipants);
}, [report, isGroupChat, isSystemChat]);
return ReportUtils.getParticipantsList(report, personalDetails, shouldOpenRoomMembersPage);
}, [report, personalDetails, shouldOpenRoomMembersPage]);
const connectedIntegration = PolicyUtils.getConnectedIntegration(policy);

// Get the active chat members by filtering out the pending members with delete action
Expand Down Expand Up @@ -307,7 +307,7 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD
isAnonymousAction: false,
shouldShowRightIcon: true,
action: () => {
if (isUserCreatedPolicyRoom || isChatThread || (isPolicyExpenseChat && isPolicyAdmin)) {
if (shouldOpenRoomMembersPage) {
Navigation.navigate(ROUTES.ROOM_MEMBERS.getRoute(report?.reportID ?? '-1'));
} else {
Navigation.navigate(ROUTES.REPORT_PARTICIPANTS.getRoute(report?.reportID ?? '-1'));
Expand Down Expand Up @@ -443,6 +443,7 @@ function ReportDetailsPage({policies, report, session, personalDetails}: ReportD
isCanceledTaskReport,
shouldShowLeaveButton,
activeChatMembers.length,
shouldOpenRoomMembersPage,
shouldShowCancelPaymentButton,
session,
leaveChat,
Expand Down
11 changes: 2 additions & 9 deletions src/pages/ReportParticipantsPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import useStyleUtils from '@hooks/useStyleUtils';
import useThemeStyles from '@hooks/useThemeStyles';
import {turnOffMobileSelectionMode} from '@libs/actions/MobileSelectionMode';
import * as Report from '@libs/actions/Report';
import Log from '@libs/Log';
import Navigation from '@libs/Navigation/Navigation';
import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils';
import * as ReportUtils from '@libs/ReportUtils';
Expand Down Expand Up @@ -51,7 +50,6 @@ function ReportParticipantsPage({report}: WithReportOrNotFoundProps) {
const currentUserAccountID = Number(session?.accountID);
const isCurrentUserAdmin = ReportUtils.isGroupChatAdmin(report, currentUserAccountID);
const isGroupChat = useMemo(() => ReportUtils.isGroupChat(report), [report]);
const isIOUReport = ReportUtils.isIOUReport(report);
const isFocused = useIsFocused();
const canSelectMultiple = isGroupChat && isCurrentUserAdmin && (isSmallScreenWidth ? selectionMode?.isEnabled : true);

Expand All @@ -64,15 +62,10 @@ function ReportParticipantsPage({report}: WithReportOrNotFoundProps) {

const getUsers = useCallback((): MemberOption[] => {
let result: MemberOption[] = [];
const shouldExcludeHiddenParticipants = !isGroupChat && !isIOUReport;
const chatParticipants = ReportUtils.getParticipantsAccountIDsForDisplay(report, shouldExcludeHiddenParticipants);
const chatParticipants = ReportUtils.getParticipantsList(report, personalDetails);
chatParticipants.forEach((accountID) => {
const role = report.participants?.[accountID].role;
const details = personalDetails?.[accountID];
if (!details) {
Log.hmmm(`[ReportParticipantsPage] no personal details found for Group chat member with accountID: ${accountID}`);
return;
}

const pendingChatMember = report?.pendingChatMembers?.findLast((member) => member.accountID === accountID.toString());
const isSelected = selectedMembers.includes(accountID) && canSelectMultiple;
Expand Down Expand Up @@ -107,7 +100,7 @@ function ReportParticipantsPage({report}: WithReportOrNotFoundProps) {

result = result.sort((a, b) => (a.text ?? '').toLowerCase().localeCompare((b.text ?? '').toLowerCase()));
return result;
}, [formatPhoneNumber, personalDetails, report, selectedMembers, currentUserAccountID, translate, isGroupChat, isIOUReport, canSelectMultiple]);
}, [formatPhoneNumber, personalDetails, report, selectedMembers, currentUserAccountID, translate, canSelectMultiple]);

const participants = useMemo(() => getUsers(), [getUsers]);

Expand Down
15 changes: 2 additions & 13 deletions src/pages/RoomMembersPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import * as DeviceCapabilities from '@libs/DeviceCapabilities';
import localeCompare from '@libs/LocaleCompare';
import Log from '@libs/Log';
import Navigation from '@libs/Navigation/Navigation';
import type {RoomMembersNavigatorParamList} from '@libs/Navigation/types';
import * as OptionsListUtils from '@libs/OptionsListUtils';
Expand Down Expand Up @@ -170,23 +169,13 @@ function RoomMembersPage({report, session, policies}: RoomMembersPageProps) {
const getMemberOptions = (): ListItem[] => {
let result: ListItem[] = [];

const participants = ReportUtils.getParticipantsAccountIDsForDisplay(report, true);
const participants = ReportUtils.getParticipantsList(report, personalDetails, true);

participants.forEach((accountID) => {
const details = personalDetails[accountID];
// When adding a new member to a room (whose personal detail does not exist in Onyx), an optimistic personal detail
// is created. However, when the real personal detail is returned from the backend, a duplicate member may appear
// briefly before the optimistic personal detail is deleted. To address this, we filter out the optimistically created
// member here.
const isDuplicateOptimisticDetail = details?.isOptimisticPersonalDetail && participants.some((accID) => accID !== accountID && details.login === personalDetails[accID]?.login);

if (!details || isDuplicateOptimisticDetail) {
Log.hmmm(`[RoomMembersPage] no personal details found for room member with accountID: ${accountID}`);
return;
}

// If search value is provided, filter out members that don't match the search value
if (searchValue.trim() && !OptionsListUtils.isSearchStringMatchUserDetails(details, searchValue)) {
if (!details || (searchValue.trim() && !OptionsListUtils.isSearchStringMatchUserDetails(details, searchValue))) {
return;
}
const pendingChatMember = report?.pendingChatMembers?.findLast((member) => member.accountID === accountID.toString());
Expand Down
Loading