From 92a626574ba95b9dd448df13967533aa41318b6c Mon Sep 17 00:00:00 2001 From: Abdelhafidh Belalia <16493223+s77rt@users.noreply.github.com> Date: Mon, 15 Apr 2024 22:01:51 +0100 Subject: [PATCH 01/21] Participants onyx migration --- src/libs/migrateOnyx.ts | 2 + src/libs/migrations/Participants.ts | 60 +++++++++++++++++++++++++++++ src/types/onyx/Report.ts | 2 - 3 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 src/libs/migrations/Participants.ts diff --git a/src/libs/migrateOnyx.ts b/src/libs/migrateOnyx.ts index 412a8e00f052..674b5b8242dc 100644 --- a/src/libs/migrateOnyx.ts +++ b/src/libs/migrateOnyx.ts @@ -2,6 +2,7 @@ import Log from './Log'; import CheckForPreviousReportActionID from './migrations/CheckForPreviousReportActionID'; import KeyReportActionsDraftByReportActionID from './migrations/KeyReportActionsDraftByReportActionID'; import NVPMigration from './migrations/NVPMigration'; +import Participants from './migrations/Participants'; import PronounsMigration from './migrations/PronounsMigration'; import RemoveEmptyReportActionsDrafts from './migrations/RemoveEmptyReportActionsDrafts'; import RenameReceiptFilename from './migrations/RenameReceiptFilename'; @@ -21,6 +22,7 @@ export default function () { RemoveEmptyReportActionsDrafts, NVPMigration, PronounsMigration, + Participants, ]; // Reduce all promises down to a single promise. All promises run in a linear fashion, waiting for the diff --git a/src/libs/migrations/Participants.ts b/src/libs/migrations/Participants.ts new file mode 100644 index 000000000000..0e0cd8722348 --- /dev/null +++ b/src/libs/migrations/Participants.ts @@ -0,0 +1,60 @@ +import Onyx from 'react-native-onyx'; +import type {NullishDeep, OnyxCollection} from 'react-native-onyx'; +import Log from '@libs/Log'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {Report} from '@src/types/onyx'; +import type {Participant, Participants} from '@src/types/onyx/Report'; + +type ReportKey = `${typeof ONYXKEYS.COLLECTION.REPORT}${string}`; +type OldReport = Report & {participantAccountIDs?: number[]; visibleChatMemberAccountIDs?: number[]}; +type OldReportCollection = Record>; + +function getReports(): Promise> { + return new Promise((resolve) => { + const connectionID = Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT, + waitForCollectionCallback: true, + callback: (reports) => { + Onyx.disconnect(connectionID); + return resolve(reports); + }, + }); + }); +} + +export default function (): Promise { + return getReports().then((reports) => { + if (!reports) { + Log.info('[Migrate Onyx] Skipped Participants migration because there are no reports'); + return; + } + + const collection = Object.entries(reports).reduce((reportsCollection, [onyxKey, report]) => { + // If we have participantAccountIDs then this report is eligible for migration + if (report?.participantAccountIDs) { + const visibleParticipants = new Set(report.visibleChatMemberAccountIDs); + const participants = report.participantAccountIDs.reduce((reportParticipants, accountID) => { + const participant: Participant = { + hidden: !visibleParticipants.has(accountID), + }; + + // eslint-disable-next-line no-param-reassign + reportParticipants[accountID] = participant; + return reportParticipants; + }, {}); + + // eslint-disable-next-line no-param-reassign + reportsCollection[onyxKey as ReportKey] = { + participants, + participantAccountIDs: null, + visibleChatMemberAccountIDs: null, + }; + } + + return reportsCollection; + }, {}); + + // eslint-disable-next-line rulesdir/prefer-actions-set-data + return Onyx.mergeCollection(ONYXKEYS.COLLECTION.REPORT, collection).then(() => Log.info('[Migrate Onyx] Ran migration Participants successfully')); + }); +} diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts index fe3eec6dc11e..b271255b06a6 100644 --- a/src/types/onyx/Report.ts +++ b/src/types/onyx/Report.ts @@ -140,8 +140,6 @@ type Report = OnyxCommon.OnyxValueWithOfflineFeedback< ownerAccountID?: number; ownerEmail?: string; participants?: Participants; - participantAccountIDs?: number[]; - visibleChatMemberAccountIDs?: number[]; total?: number; unheldTotal?: number; currency?: string; From 07356fb2df7177c280810d0d5991113a69b3d1ef Mon Sep 17 00:00:00 2001 From: Abdelhafidh Belalia <16493223+s77rt@users.noreply.github.com> Date: Sun, 21 Apr 2024 19:52:26 +0100 Subject: [PATCH 02/21] Participants migration: ReportUtils, OptionsListUtils, ReportWelcomeText --- diff.tmp | 523 +++++++++++++++++++++++++++ src/components/ReportWelcomeText.tsx | 2 +- src/libs/OptionsListUtils.ts | 54 ++- src/libs/ReportUtils.ts | 149 ++++---- 4 files changed, 649 insertions(+), 79 deletions(-) create mode 100644 diff.tmp diff --git a/diff.tmp b/diff.tmp new file mode 100644 index 000000000000..286451f9e999 --- /dev/null +++ b/diff.tmp @@ -0,0 +1,523 @@ +diff --git a/src/components/ReportWelcomeText.tsx b/src/components/ReportWelcomeText.tsx +index 219199c25b..3adc5feafa 100644 +--- a/src/components/ReportWelcomeText.tsx ++++ b/src/components/ReportWelcomeText.tsx +@@ -39,7 +39,7 @@ function ReportWelcomeText({report, policy, personalDetails}: ReportWelcomeTextP + const isChatRoom = ReportUtils.isChatRoom(report); + const isSelfDM = ReportUtils.isSelfDM(report); + const isDefault = !(isChatRoom || isPolicyExpenseChat || isSelfDM); +- const participantAccountIDs = report?.participantAccountIDs ?? []; ++ const participantAccountIDs = Object.keys(report?.participants ?? {}).map(Number); + const isMultipleParticipant = participantAccountIDs.length > 1; + const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips(OptionsListUtils.getPersonalDetailsForAccountIDs(participantAccountIDs, personalDetails), isMultipleParticipant); + const isUserPolicyAdmin = PolicyUtils.isPolicyAdmin(policy); +diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts +index 2aad4179c3..a229b74f7f 100644 +--- a/src/libs/OptionsListUtils.ts ++++ b/src/libs/OptionsListUtils.ts +@@ -476,14 +476,15 @@ function getSearchText( + const chatRoomSubtitle = ReportUtils.getChatRoomSubtitle(report); + + Array.prototype.push.apply(searchTerms, chatRoomSubtitle?.split(/[,\s]/) ?? ['']); +- } else { +- const visibleChatMemberAccountIDs = report.visibleChatMemberAccountIDs ?? []; +- if (allPersonalDetails) { +- for (const accountID of visibleChatMemberAccountIDs) { +- const login = allPersonalDetails[accountID]?.login; +- if (login) { +- searchTerms = searchTerms.concat(login); +- } ++ } else if (allPersonalDetails) { ++ const visibleParticipantAccountIDs = Object.entries(report.participants ?? {}) ++ .filter(([_, participant]) => participant && !participant.hidden) ++ .map(([accountID]) => Number(accountID)); ++ ++ for (const accountID of visibleParticipantAccountIDs) { ++ const login = allPersonalDetails[accountID]?.login; ++ if (login) { ++ searchTerms = searchTerms.concat(login); + } + } + } +@@ -708,11 +709,16 @@ function createOption( + result.isPinned = report.isPinned; + result.iouReportID = report.iouReportID; + result.keyForList = String(report.reportID); +- result.tooltipText = ReportUtils.getReportParticipantsTitle(report.visibleChatMemberAccountIDs ?? []); + result.isWaitingOnBankAccount = report.isWaitingOnBankAccount; + result.policyID = report.policyID; + result.isSelfDM = ReportUtils.isSelfDM(report); + ++ const visibleParticipantAccountIDs = Object.entries(report.participants ?? {}) ++ .filter(([_, participant]) => participant && !participant.hidden) ++ .map(([accountID]) => Number(accountID)); ++ ++ result.tooltipText = ReportUtils.getReportParticipantsTitle(visibleParticipantAccountIDs); ++ + hasMultipleParticipants = personalDetailList.length > 1 || result.isChatRoom || result.isPolicyExpenseChat; + subtitle = ReportUtils.getChatRoomSubtitle(report); + +@@ -776,8 +782,12 @@ function createOption( + function getReportOption(participant: Participant): ReportUtils.OptionData { + const report = ReportUtils.getReport(participant.reportID); + ++ const visibleParticipantAccountIDs = Object.entries(report?.participants ?? {}) ++ .filter(([_, participant]) => participant && !participant.hidden) ++ .map(([accountID]) => Number(accountID)); ++ + const option = createOption( +- report?.visibleChatMemberAccountIDs ?? [], ++ visibleParticipantAccountIDs, + allPersonalDetails ?? {}, + !isEmptyObject(report) ? report : null, + {}, +@@ -805,8 +815,12 @@ function getReportOption(participant: Participant): ReportUtils.OptionData { + function getPolicyExpenseReportOption(participant: Participant | ReportUtils.OptionData): ReportUtils.OptionData { + const expenseReport = ReportUtils.isPolicyExpenseChat(participant) ? ReportUtils.getReport(participant.reportID) : null; + ++ const visibleParticipantAccountIDs = Object.entries(expenseReport?.participants ?? {}) ++ .filter(([_, participant]) => participant && !participant.hidden) ++ .map(([accountID]) => Number(accountID)); ++ + const option = createOption( +- expenseReport?.visibleChatMemberAccountIDs ?? [], ++ visibleParticipantAccountIDs, + allPersonalDetails ?? {}, + !isEmptyObject(expenseReport) ? expenseReport : null, + {}, +@@ -1472,8 +1486,12 @@ function createOptionList(personalDetails: OnyxEntry, repor + } + + const isSelfDM = ReportUtils.isSelfDM(report); +- // Currently, currentUser is not included in visibleChatMemberAccountIDs, so for selfDM we need to add the currentUser as participants. +- const accountIDs = isSelfDM ? [currentUserAccountID ?? 0] : report.visibleChatMemberAccountIDs ?? []; ++ const visibleParticipantAccountIDs = Object.entries(report.participants ?? {}) ++ .filter(([_, participant]) => participant && !participant.hidden) ++ .map(([accountID]) => Number(accountID)); ++ ++ // Currently, currentUser is not included in visibleParticipantAccountIDs, so for selfDM we need to add the currentUser as participants. ++ const accountIDs = isSelfDM ? [currentUserAccountID ?? 0] : visibleParticipantAccountIDs ?? []; + + if (!accountIDs || accountIDs.length === 0) { + return; +@@ -1505,7 +1523,7 @@ function createOptionList(personalDetails: OnyxEntry, repor + } + + function createOptionFromReport(report: Report, personalDetails: OnyxEntry) { +- const accountIDs = report.participantAccountIDs ?? []; ++ const accountIDs = Object.keys(report.participants ?? {}).map(Number); + + return { + item: report, +@@ -1692,8 +1710,12 @@ function getOptions( + const isPolicyExpenseChat = option.isPolicyExpenseChat; + const isMoneyRequestReport = option.isMoneyRequestReport; + const isSelfDM = option.isSelfDM; +- // Currently, currentUser is not included in visibleChatMemberAccountIDs, so for selfDM we need to add the currentUser as participants. +- const accountIDs = isSelfDM ? [currentUserAccountID ?? 0] : report.visibleChatMemberAccountIDs ?? []; ++ const visibleParticipantAccountIDs = Object.entries(report.participants ?? {}) ++ .filter(([_, participant]) => participant && !participant.hidden) ++ .map(([accountID]) => Number(accountID)); ++ ++ // Currently, currentUser is not included in visibleParticipantAccountIDs, so for selfDM we need to add the currentUser as participants. ++ const accountIDs = isSelfDM ? [currentUserAccountID ?? 0] : visibleParticipantAccountIDs ?? []; + + if (isPolicyExpenseChat && report.isOwnPolicyExpenseChat && !includeOwnedWorkspaceChats) { + return; +diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts +index af55b4ca29..d8c2c08e65 100644 +--- a/src/libs/ReportUtils.ts ++++ b/src/libs/ReportUtils.ts +@@ -272,8 +272,6 @@ type OptimisticChatReport = Pick< + | 'parentReportActionID' + | 'parentReportID' + | 'participants' +- | 'participantAccountIDs' +- | 'visibleChatMemberAccountIDs' + | 'policyID' + | 'reportID' + | 'reportName' +@@ -333,8 +331,7 @@ type OptimisticTaskReport = Pick< + | 'reportName' + | 'description' + | 'ownerAccountID' +- | 'participantAccountIDs' +- | 'visibleChatMemberAccountIDs' ++ | 'participants' + | 'managerID' + | 'type' + | 'parentReportID' +@@ -373,8 +370,7 @@ type OptimisticIOUReport = Pick< + | 'managerID' + | 'policyID' + | 'ownerAccountID' +- | 'participantAccountIDs' +- | 'visibleChatMemberAccountIDs' ++ | 'participants' + | 'reportID' + | 'stateNum' + | 'statusNum' +@@ -976,7 +972,8 @@ function isGroupChat(report: OnyxEntry | Partial): boolean { + * Only returns true if this is our main 1:1 DM report with Concierge + */ + function isConciergeChatReport(report: OnyxEntry): boolean { +- return report?.participantAccountIDs?.length === 1 && Number(report.participantAccountIDs?.[0]) === CONST.ACCOUNT_ID.CONCIERGE && !isChatThread(report); ++ const participantAccountIDs = Object.keys(report?.participants ?? {}).map(Number); ++ return participantAccountIDs.length === 1 && participantAccountIDs[0] === CONST.ACCOUNT_ID.CONCIERGE && !isChatThread(report); + } + + function findSelfDMReportID(): string | undefined { +@@ -1032,7 +1029,7 @@ function isProcessingReport(report: OnyxEntry | EmptyObject): boolean { + * and personal detail of participant is optimistic data + */ + function shouldDisableDetailPage(report: OnyxEntry): boolean { +- const participantAccountIDs = report?.participantAccountIDs ?? []; ++ const participantAccountIDs = Object.keys(report?.participants ?? {}).map(Number); + + if (isChatRoom(report) || isPolicyExpenseChat(report) || isChatThread(report) || isTaskReport(report)) { + return false; +@@ -1047,8 +1044,10 @@ function shouldDisableDetailPage(report: OnyxEntry): boolean { + * Returns true if this report has only one participant and it's an Expensify account. + */ + function isExpensifyOnlyParticipantInReport(report: OnyxEntry): boolean { +- const reportParticipants = report?.participantAccountIDs?.filter((accountID) => accountID !== currentUserAccountID) ?? []; +- return reportParticipants.length === 1 && reportParticipants.some((accountID) => CONST.EXPENSIFY_ACCOUNT_IDS.includes(accountID)); ++ const otherParticipants = Object.keys(report?.participants ?? {}) ++ .map(Number) ++ .filter((accountID) => accountID !== currentUserAccountID); ++ return otherParticipants.length === 1 && otherParticipants.some((accountID) => CONST.EXPENSIFY_ACCOUNT_IDS.includes(accountID)); + } + + /** +@@ -1057,8 +1056,10 @@ function isExpensifyOnlyParticipantInReport(report: OnyxEntry): boolean + * + */ + function canCreateTaskInReport(report: OnyxEntry): boolean { +- const otherReportParticipants = report?.participantAccountIDs?.filter((accountID) => accountID !== currentUserAccountID) ?? []; +- const areExpensifyAccountsOnlyOtherParticipants = otherReportParticipants?.length >= 1 && otherReportParticipants?.every((accountID) => CONST.EXPENSIFY_ACCOUNT_IDS.includes(accountID)); ++ const otherParticipants = Object.keys(report?.participants ?? {}) ++ .map(Number) ++ .filter((accountID) => accountID !== currentUserAccountID); ++ const areExpensifyAccountsOnlyOtherParticipants = otherParticipants.length >= 1 && otherParticipants.every((accountID) => CONST.EXPENSIFY_ACCOUNT_IDS.includes(accountID)); + if (areExpensifyAccountsOnlyOtherParticipants && isDM(report)) { + return false; + } +@@ -1112,7 +1113,7 @@ function findLastAccessedReport( + // Check where ReportUtils.findLastAccessedReport is called in MainDrawerNavigator.js for more context. + // Domain rooms are now the only type of default room that are on the defaultRooms beta. + sortedReports = sortedReports.filter( +- (report) => !isDomainRoom(report) || getPolicyType(report, policies) === CONST.POLICY.TYPE.FREE || hasExpensifyGuidesEmails(report?.participantAccountIDs ?? []), ++ (report) => !isDomainRoom(report) || getPolicyType(report, policies) === CONST.POLICY.TYPE.FREE || hasExpensifyGuidesEmails(Object.keys(report?.participants ?? {}).map(Number)), + ); + } + +@@ -1215,7 +1216,7 @@ function isPolicyAdmin(policyID: string, policies: OnyxCollection): bool + * Returns true if report has a single participant. + */ + function hasSingleParticipant(report: OnyxEntry): boolean { +- return report?.participantAccountIDs?.length === 1; ++ return Object.keys(report?.participants ?? {}).length === 1; + } + + /** +@@ -1330,7 +1331,7 @@ function isOneTransactionThread(reportID: string, parentReportID: string): boole + * + */ + function isOneOnOneChat(report: OnyxEntry): boolean { +- const participantAccountIDs = report?.participantAccountIDs ?? []; ++ const participantAccountIDs = Object.keys(report?.participants ?? {}); + return ( + !isChatRoom(report) && + !isExpenseRequest(report) && +@@ -1492,7 +1493,8 @@ function getRoomWelcomeMessage(report: OnyxEntry, isUserPolicyAdmin: boo + * Returns true if Concierge is one of the chat participants (1:1 as well as group chats) + */ + function chatIncludesConcierge(report: Partial>): boolean { +- return Boolean(report?.participantAccountIDs?.length && report?.participantAccountIDs?.includes(CONST.ACCOUNT_ID.CONCIERGE)); ++ const participantAccountIDs = Object.keys(report?.participants ?? {}).map(Number); ++ return participantAccountIDs.includes(CONST.ACCOUNT_ID.CONCIERGE); + } + + /** +@@ -1513,24 +1515,32 @@ function getReportRecipientAccountIDs(report: OnyxEntry, currentLoginAcc + } + } + +- let finalParticipantAccountIDs: number[] | undefined = []; ++ let finalParticipantAccountIDs: number[] = []; + if (isMoneyRequestReport(report)) { +- // For money requests i.e the IOU (1:1 person) and Expense (1:* person) reports, use the full `initialParticipantAccountIDs` array +- // and add the `ownerAccountId`. Money request reports don't add `ownerAccountId` in `participantAccountIDs` array +- const defaultParticipantAccountIDs = finalReport?.participantAccountIDs ?? []; ++ // For money requests i.e the IOU (1:1 person) and Expense (1:* person) reports, use the full `participants` ++ // and add the `ownerAccountId`. Money request reports don't add `ownerAccountId` in `participants` array ++ const defaultParticipantAccountIDs = Object.keys(finalReport?.participants ?? {}).map(Number); + const setOfParticipantAccountIDs = new Set(report?.ownerAccountID ? [...defaultParticipantAccountIDs, report.ownerAccountID] : defaultParticipantAccountIDs); + finalParticipantAccountIDs = [...setOfParticipantAccountIDs]; + } else if (isTaskReport(report)) { +- // Task reports `managerID` will change when assignee is changed, in that case the old `managerID` is still present in `participantAccountIDs` +- // array along with the new one. We only need the `managerID` as a participant here. ++ // Task reports `managerID` will change when assignee is changed, in that case the old `managerID` is still present in `participants` ++ // along with the new one. We only need the `managerID` as a participant here. + finalParticipantAccountIDs = report?.managerID ? [report?.managerID] : []; + } else { +- finalParticipantAccountIDs = finalReport?.participantAccountIDs; ++ finalParticipantAccountIDs = Object.keys(finalReport?.participants ?? {}).map(Number); + } + +- const reportParticipants = finalParticipantAccountIDs?.filter((accountID) => accountID !== currentLoginAccountID) ?? []; +- const participantsWithoutExpensifyAccountIDs = reportParticipants.filter((participant) => !CONST.EXPENSIFY_ACCOUNT_IDS.includes(participant ?? 0)); +- return participantsWithoutExpensifyAccountIDs; ++ const otherParticipantsWithoutExpensifyAccountIDs = finalParticipantAccountIDs.filter((accountID) => { ++ if (accountID === currentLoginAccountID) { ++ return false; ++ } ++ if (CONST.EXPENSIFY_ACCOUNT_IDS.includes(accountID)) { ++ return false; ++ } ++ return true; ++ }); ++ ++ return otherParticipantsWithoutExpensifyAccountIDs; + } + + /** +@@ -1935,7 +1945,9 @@ function getIcons( + return [groupChatIcon]; + } + +- return getIconsForParticipants(report?.participantAccountIDs ?? [], personalDetails); ++ const participantAccountIDs = Object.keys(report.participants ?? {}).map(Number); ++ ++ return getIconsForParticipants(participantAccountIDs, personalDetails); + } + + function getDisplayNamesWithTooltips( +@@ -3014,12 +3026,11 @@ function getReportName(report: OnyxEntry, policy: OnyxEntry = nu + } + + // Not a room or PolicyExpenseChat, generate title from first 5 other participants +- const participantAccountIDs = report?.participantAccountIDs?.slice(0, 6) ?? []; +- const participantsWithoutCurrentUser = participantAccountIDs.filter((accountID) => accountID !== currentUserAccountID); ++ const participantsWithoutCurrentUser = Object.keys(report?.participants ?? {}) ++ .map(Number) ++ .filter((accountID) => accountID !== currentUserAccountID) ++ .slice(0, 5); + const isMultipleParticipantReport = participantsWithoutCurrentUser.length > 1; +- if (participantsWithoutCurrentUser.length > 5) { +- participantsWithoutCurrentUser.pop(); +- } + return participantsWithoutCurrentUser.map((accountID) => getDisplayNameForParticipant(accountID, isMultipleParticipantReport)).join(', '); + } + +@@ -3031,8 +3042,10 @@ function getPayeeName(report: OnyxEntry): string | undefined { + return undefined; + } + +- const participantAccountIDs = report?.participantAccountIDs ?? []; +- const participantsWithoutCurrentUser = participantAccountIDs.filter((accountID) => accountID !== currentUserAccountID); ++ const participantsWithoutCurrentUser = Object.keys(report?.participants ?? {}) ++ .map(Number) ++ .filter((accountID) => accountID !== currentUserAccountID); ++ + if (participantsWithoutCurrentUser.length === 0) { + return undefined; + } +@@ -3089,15 +3102,14 @@ function getParentNavigationSubtitle(report: OnyxEntry): ParentNavigatio + * Navigate to the details page of a given report + */ + function navigateToDetailsPage(report: OnyxEntry) { +- const participantAccountIDs = report?.participantAccountIDs ?? []; +- + if (isSelfDM(report)) { + Navigation.navigate(ROUTES.PROFILE.getRoute(currentUserAccountID ?? 0)); + return; + } + + if (isOneOnOneChat(report)) { +- Navigation.navigate(ROUTES.PROFILE.getRoute(participantAccountIDs[0])); ++ const participantAccountID = Object.keys(report?.participants ?? {})[0]; ++ Navigation.navigate(ROUTES.PROFILE.getRoute(participantAccountID)); + return; + } + if (report?.reportID) { +@@ -3110,7 +3122,8 @@ function navigateToDetailsPage(report: OnyxEntry) { + */ + function goBackToDetailsPage(report: OnyxEntry) { + if (isOneOnOneChat(report)) { +- Navigation.goBack(ROUTES.PROFILE.getRoute(report?.participantAccountIDs?.[0] ?? '')); ++ const participantAccountID = Object.keys(report?.participants ?? {})[0]; ++ Navigation.goBack(ROUTES.PROFILE.getRoute(participantAccountID)); + return; + } + Navigation.goBack(ROUTES.REPORT_SETTINGS.getRoute(report?.reportID ?? '')); +@@ -3333,8 +3346,10 @@ function buildOptimisticIOUReport(payeeAccountID: number, payerAccountID: number + const personalDetails = getPersonalDetailsForAccountID(payerAccountID); + const payerEmail = 'login' in personalDetails ? personalDetails.login : ''; + +- // When creating a report the participantsAccountIDs and visibleChatMemberAccountIDs are the same +- const participantsAccountIDs = [payeeAccountID, payerAccountID]; ++ const participants: Participants = { ++ [payeeAccountID]: {hidden: false}, ++ [payerAccountID]: {hidden: false}, ++ }; + + return { + type: CONST.REPORT.TYPE.IOU, +@@ -3343,8 +3358,7 @@ function buildOptimisticIOUReport(payeeAccountID: number, payerAccountID: number + currency, + managerID: payerAccountID, + ownerAccountID: payeeAccountID, +- participantAccountIDs: participantsAccountIDs, +- visibleChatMemberAccountIDs: participantsAccountIDs, ++ participants, + reportID: generateReportID(), + stateNum: isSendingMoney ? CONST.REPORT.STATE_NUM.APPROVED : CONST.REPORT.STATE_NUM.SUBMITTED, + statusNum: isSendingMoney ? CONST.REPORT.STATUS_NUM.REIMBURSED : CONST.REPORT.STATE_NUM.SUBMITTED, +@@ -3962,10 +3976,6 @@ function buildOptimisticChatReport( + ownerAccountID: ownerAccountID || CONST.REPORT.OWNER_ACCOUNT_ID_FAKE, + parentReportActionID, + parentReportID, +- // When creating a report the participantsAccountIDs and visibleChatMemberAccountIDs are the same +- participantAccountIDs: participantList, +- visibleChatMemberAccountIDs: participantList, +- // For group chats we need to have participants object as we are migrating away from `participantAccountIDs` and `visibleChatMemberAccountIDs`. See https://github.com/Expensify/App/issues/34692 + participants, + policyID, + reportID: optimisticReportID || generateReportID(), +@@ -4364,16 +4374,21 @@ function buildOptimisticTaskReport( + description?: string, + policyID: string = CONST.POLICY.OWNER_EMAIL_FAKE, + ): OptimisticTaskReport { +- // When creating a report the participantsAccountIDs and visibleChatMemberAccountIDs are the same +- const participantsAccountIDs = assigneeAccountID && assigneeAccountID !== ownerAccountID ? [assigneeAccountID] : []; ++ const participants: Participants = ++ assigneeAccountID && assigneeAccountID !== ownerAccountID ++ ? { ++ [assigneeAccountID]: { ++ hidden: false, ++ }, ++ } ++ : {}; + + return { + reportID: generateReportID(), + reportName: title, + description, + ownerAccountID, +- participantAccountIDs: participantsAccountIDs, +- visibleChatMemberAccountIDs: participantsAccountIDs, ++ participants, + managerID: assigneeAccountID, + type: CONST.REPORT.TYPE.TASK, + parentReportID, +@@ -4535,7 +4550,7 @@ function canSeeDefaultRoom(report: OnyxEntry, policies: OnyxCollection

{ ++ const participantAccountIDs = Object.keys(report?.participants ?? {}); ++ + // If the report has been deleted, or there are no participants (like an empty #admins room) then skip it + if ( +- !report || +- report.participantAccountIDs?.length === 0 || ++ participantAccountIDs.length === 0 || + isChatThread(report) || + isTaskReport(report) || + isMoneyRequestReport(report) || +@@ -4761,8 +4779,10 @@ function getChatByParticipants(newParticipantList: number[], reports: OnyxCollec + return false; + } + ++ const sortedParticipantsAccountIDs = participantAccountIDs.map(Number).sort(); ++ + // Only return the chat if it has all the participants +- return lodashIsEqual(sortedNewParticipantList, report.participantAccountIDs?.sort()); ++ return lodashIsEqual(sortedNewParticipantList, sortedParticipantsAccountIDs); + }) ?? null + ); + } +@@ -4774,11 +4794,15 @@ function getChatByParticipantsAndPolicy(newParticipantList: number[], policyID: + newParticipantList.sort(); + return ( + Object.values(allReports ?? {}).find((report) => { ++ const participantAccountIDs = Object.keys(report?.participants ?? {}); ++ + // If the report has been deleted, or there are no participants (like an empty #admins room) then skip it +- if (!report?.participantAccountIDs) { ++ if (!report || participantAccountIDs.length === 0) { + return false; + } +- const sortedParticipantsAccountIDs = report.participantAccountIDs?.sort(); ++ ++ const sortedParticipantsAccountIDs = participantAccountIDs.map(Number).sort(); ++ + // Only return the room if it has all the participants and is not a policy room + return report.policyID === policyID && newParticipantList.every((newParticipant) => sortedParticipantsAccountIDs.includes(newParticipant)); + }) ?? null +@@ -4793,7 +4817,8 @@ function getAllPolicyReports(policyID: string): Array> { + * Returns true if Chronos is one of the chat participants (1:1) + */ + function chatIncludesChronos(report: OnyxEntry | EmptyObject): boolean { +- return Boolean(report?.participantAccountIDs?.includes(CONST.ACCOUNT_ID.CHRONOS)); ++ const participantAccountIDs = Object.keys(report?.participants ?? {}).map(Number); ++ return participantAccountIDs.includes(CONST.ACCOUNT_ID.CHRONOS); + } + + /** +@@ -5257,7 +5282,7 @@ function getReportOfflinePendingActionAndErrors(report: OnyxEntry): Repo + * Check if the report can create the expense with type is iouType + */ + function canCreateRequest(report: OnyxEntry, policy: OnyxEntry, iouType: (typeof CONST.IOU.TYPE)[keyof typeof CONST.IOU.TYPE]): boolean { +- const participantAccountIDs = report?.participantAccountIDs ?? []; ++ const participantAccountIDs = Object.keys(report?.participants ?? {}).map(Number); + if (!canUserPerformWriteAction(report)) { + return false; + } +@@ -5515,7 +5540,7 @@ function isDeprecatedGroupDM(report: OnyxEntry): boolean { + !isMoneyRequestReport(report) && + !isArchivedRoom(report) && + !Object.values(CONST.REPORT.CHAT_TYPE).some((chatType) => chatType === getChatType(report)) && +- (report.participantAccountIDs?.length ?? 0) > 1, ++ Object.keys(report.participants ?? {}).length > 1, + ); + } + +@@ -5539,7 +5564,7 @@ function isReportParticipant(accountID: number, report: OnyxEntry): bool + return true; + } + +- const possibleAccountIDs = report?.participantAccountIDs ?? []; ++ const possibleAccountIDs = Object.keys(report?.participants ?? {}).map(Number); + if (report?.ownerAccountID) { + possibleAccountIDs.push(report?.ownerAccountID); + } diff --git a/src/components/ReportWelcomeText.tsx b/src/components/ReportWelcomeText.tsx index 219199c25bc3..3adc5feafab7 100644 --- a/src/components/ReportWelcomeText.tsx +++ b/src/components/ReportWelcomeText.tsx @@ -39,7 +39,7 @@ function ReportWelcomeText({report, policy, personalDetails}: ReportWelcomeTextP const isChatRoom = ReportUtils.isChatRoom(report); const isSelfDM = ReportUtils.isSelfDM(report); const isDefault = !(isChatRoom || isPolicyExpenseChat || isSelfDM); - const participantAccountIDs = report?.participantAccountIDs ?? []; + const participantAccountIDs = Object.keys(report?.participants ?? {}).map(Number); const isMultipleParticipant = participantAccountIDs.length > 1; const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips(OptionsListUtils.getPersonalDetailsForAccountIDs(participantAccountIDs, personalDetails), isMultipleParticipant); const isUserPolicyAdmin = PolicyUtils.isPolicyAdmin(policy); diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 2aad4179c337..a229b74f7f96 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -476,14 +476,15 @@ function getSearchText( const chatRoomSubtitle = ReportUtils.getChatRoomSubtitle(report); Array.prototype.push.apply(searchTerms, chatRoomSubtitle?.split(/[,\s]/) ?? ['']); - } else { - const visibleChatMemberAccountIDs = report.visibleChatMemberAccountIDs ?? []; - if (allPersonalDetails) { - for (const accountID of visibleChatMemberAccountIDs) { - const login = allPersonalDetails[accountID]?.login; - if (login) { - searchTerms = searchTerms.concat(login); - } + } else if (allPersonalDetails) { + const visibleParticipantAccountIDs = Object.entries(report.participants ?? {}) + .filter(([_, participant]) => participant && !participant.hidden) + .map(([accountID]) => Number(accountID)); + + for (const accountID of visibleParticipantAccountIDs) { + const login = allPersonalDetails[accountID]?.login; + if (login) { + searchTerms = searchTerms.concat(login); } } } @@ -708,11 +709,16 @@ function createOption( result.isPinned = report.isPinned; result.iouReportID = report.iouReportID; result.keyForList = String(report.reportID); - result.tooltipText = ReportUtils.getReportParticipantsTitle(report.visibleChatMemberAccountIDs ?? []); result.isWaitingOnBankAccount = report.isWaitingOnBankAccount; result.policyID = report.policyID; result.isSelfDM = ReportUtils.isSelfDM(report); + const visibleParticipantAccountIDs = Object.entries(report.participants ?? {}) + .filter(([_, participant]) => participant && !participant.hidden) + .map(([accountID]) => Number(accountID)); + + result.tooltipText = ReportUtils.getReportParticipantsTitle(visibleParticipantAccountIDs); + hasMultipleParticipants = personalDetailList.length > 1 || result.isChatRoom || result.isPolicyExpenseChat; subtitle = ReportUtils.getChatRoomSubtitle(report); @@ -776,8 +782,12 @@ function createOption( function getReportOption(participant: Participant): ReportUtils.OptionData { const report = ReportUtils.getReport(participant.reportID); + const visibleParticipantAccountIDs = Object.entries(report?.participants ?? {}) + .filter(([_, participant]) => participant && !participant.hidden) + .map(([accountID]) => Number(accountID)); + const option = createOption( - report?.visibleChatMemberAccountIDs ?? [], + visibleParticipantAccountIDs, allPersonalDetails ?? {}, !isEmptyObject(report) ? report : null, {}, @@ -805,8 +815,12 @@ function getReportOption(participant: Participant): ReportUtils.OptionData { function getPolicyExpenseReportOption(participant: Participant | ReportUtils.OptionData): ReportUtils.OptionData { const expenseReport = ReportUtils.isPolicyExpenseChat(participant) ? ReportUtils.getReport(participant.reportID) : null; + const visibleParticipantAccountIDs = Object.entries(expenseReport?.participants ?? {}) + .filter(([_, participant]) => participant && !participant.hidden) + .map(([accountID]) => Number(accountID)); + const option = createOption( - expenseReport?.visibleChatMemberAccountIDs ?? [], + visibleParticipantAccountIDs, allPersonalDetails ?? {}, !isEmptyObject(expenseReport) ? expenseReport : null, {}, @@ -1472,8 +1486,12 @@ function createOptionList(personalDetails: OnyxEntry, repor } const isSelfDM = ReportUtils.isSelfDM(report); - // Currently, currentUser is not included in visibleChatMemberAccountIDs, so for selfDM we need to add the currentUser as participants. - const accountIDs = isSelfDM ? [currentUserAccountID ?? 0] : report.visibleChatMemberAccountIDs ?? []; + const visibleParticipantAccountIDs = Object.entries(report.participants ?? {}) + .filter(([_, participant]) => participant && !participant.hidden) + .map(([accountID]) => Number(accountID)); + + // Currently, currentUser is not included in visibleParticipantAccountIDs, so for selfDM we need to add the currentUser as participants. + const accountIDs = isSelfDM ? [currentUserAccountID ?? 0] : visibleParticipantAccountIDs ?? []; if (!accountIDs || accountIDs.length === 0) { return; @@ -1505,7 +1523,7 @@ function createOptionList(personalDetails: OnyxEntry, repor } function createOptionFromReport(report: Report, personalDetails: OnyxEntry) { - const accountIDs = report.participantAccountIDs ?? []; + const accountIDs = Object.keys(report.participants ?? {}).map(Number); return { item: report, @@ -1692,8 +1710,12 @@ function getOptions( const isPolicyExpenseChat = option.isPolicyExpenseChat; const isMoneyRequestReport = option.isMoneyRequestReport; const isSelfDM = option.isSelfDM; - // Currently, currentUser is not included in visibleChatMemberAccountIDs, so for selfDM we need to add the currentUser as participants. - const accountIDs = isSelfDM ? [currentUserAccountID ?? 0] : report.visibleChatMemberAccountIDs ?? []; + const visibleParticipantAccountIDs = Object.entries(report.participants ?? {}) + .filter(([_, participant]) => participant && !participant.hidden) + .map(([accountID]) => Number(accountID)); + + // Currently, currentUser is not included in visibleParticipantAccountIDs, so for selfDM we need to add the currentUser as participants. + const accountIDs = isSelfDM ? [currentUserAccountID ?? 0] : visibleParticipantAccountIDs ?? []; if (isPolicyExpenseChat && report.isOwnPolicyExpenseChat && !includeOwnedWorkspaceChats) { return; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index af55b4ca29be..d8c2c08e65ce 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -272,8 +272,6 @@ type OptimisticChatReport = Pick< | 'parentReportActionID' | 'parentReportID' | 'participants' - | 'participantAccountIDs' - | 'visibleChatMemberAccountIDs' | 'policyID' | 'reportID' | 'reportName' @@ -333,8 +331,7 @@ type OptimisticTaskReport = Pick< | 'reportName' | 'description' | 'ownerAccountID' - | 'participantAccountIDs' - | 'visibleChatMemberAccountIDs' + | 'participants' | 'managerID' | 'type' | 'parentReportID' @@ -373,8 +370,7 @@ type OptimisticIOUReport = Pick< | 'managerID' | 'policyID' | 'ownerAccountID' - | 'participantAccountIDs' - | 'visibleChatMemberAccountIDs' + | 'participants' | 'reportID' | 'stateNum' | 'statusNum' @@ -976,7 +972,8 @@ function isGroupChat(report: OnyxEntry | Partial): boolean { * Only returns true if this is our main 1:1 DM report with Concierge */ function isConciergeChatReport(report: OnyxEntry): boolean { - return report?.participantAccountIDs?.length === 1 && Number(report.participantAccountIDs?.[0]) === CONST.ACCOUNT_ID.CONCIERGE && !isChatThread(report); + const participantAccountIDs = Object.keys(report?.participants ?? {}).map(Number); + return participantAccountIDs.length === 1 && participantAccountIDs[0] === CONST.ACCOUNT_ID.CONCIERGE && !isChatThread(report); } function findSelfDMReportID(): string | undefined { @@ -1032,7 +1029,7 @@ function isProcessingReport(report: OnyxEntry | EmptyObject): boolean { * and personal detail of participant is optimistic data */ function shouldDisableDetailPage(report: OnyxEntry): boolean { - const participantAccountIDs = report?.participantAccountIDs ?? []; + const participantAccountIDs = Object.keys(report?.participants ?? {}).map(Number); if (isChatRoom(report) || isPolicyExpenseChat(report) || isChatThread(report) || isTaskReport(report)) { return false; @@ -1047,8 +1044,10 @@ function shouldDisableDetailPage(report: OnyxEntry): boolean { * Returns true if this report has only one participant and it's an Expensify account. */ function isExpensifyOnlyParticipantInReport(report: OnyxEntry): boolean { - const reportParticipants = report?.participantAccountIDs?.filter((accountID) => accountID !== currentUserAccountID) ?? []; - return reportParticipants.length === 1 && reportParticipants.some((accountID) => CONST.EXPENSIFY_ACCOUNT_IDS.includes(accountID)); + const otherParticipants = Object.keys(report?.participants ?? {}) + .map(Number) + .filter((accountID) => accountID !== currentUserAccountID); + return otherParticipants.length === 1 && otherParticipants.some((accountID) => CONST.EXPENSIFY_ACCOUNT_IDS.includes(accountID)); } /** @@ -1057,8 +1056,10 @@ function isExpensifyOnlyParticipantInReport(report: OnyxEntry): boolean * */ function canCreateTaskInReport(report: OnyxEntry): boolean { - const otherReportParticipants = report?.participantAccountIDs?.filter((accountID) => accountID !== currentUserAccountID) ?? []; - const areExpensifyAccountsOnlyOtherParticipants = otherReportParticipants?.length >= 1 && otherReportParticipants?.every((accountID) => CONST.EXPENSIFY_ACCOUNT_IDS.includes(accountID)); + const otherParticipants = Object.keys(report?.participants ?? {}) + .map(Number) + .filter((accountID) => accountID !== currentUserAccountID); + const areExpensifyAccountsOnlyOtherParticipants = otherParticipants.length >= 1 && otherParticipants.every((accountID) => CONST.EXPENSIFY_ACCOUNT_IDS.includes(accountID)); if (areExpensifyAccountsOnlyOtherParticipants && isDM(report)) { return false; } @@ -1112,7 +1113,7 @@ function findLastAccessedReport( // Check where ReportUtils.findLastAccessedReport is called in MainDrawerNavigator.js for more context. // Domain rooms are now the only type of default room that are on the defaultRooms beta. sortedReports = sortedReports.filter( - (report) => !isDomainRoom(report) || getPolicyType(report, policies) === CONST.POLICY.TYPE.FREE || hasExpensifyGuidesEmails(report?.participantAccountIDs ?? []), + (report) => !isDomainRoom(report) || getPolicyType(report, policies) === CONST.POLICY.TYPE.FREE || hasExpensifyGuidesEmails(Object.keys(report?.participants ?? {}).map(Number)), ); } @@ -1215,7 +1216,7 @@ function isPolicyAdmin(policyID: string, policies: OnyxCollection): bool * Returns true if report has a single participant. */ function hasSingleParticipant(report: OnyxEntry): boolean { - return report?.participantAccountIDs?.length === 1; + return Object.keys(report?.participants ?? {}).length === 1; } /** @@ -1330,7 +1331,7 @@ function isOneTransactionThread(reportID: string, parentReportID: string): boole * */ function isOneOnOneChat(report: OnyxEntry): boolean { - const participantAccountIDs = report?.participantAccountIDs ?? []; + const participantAccountIDs = Object.keys(report?.participants ?? {}); return ( !isChatRoom(report) && !isExpenseRequest(report) && @@ -1492,7 +1493,8 @@ function getRoomWelcomeMessage(report: OnyxEntry, isUserPolicyAdmin: boo * Returns true if Concierge is one of the chat participants (1:1 as well as group chats) */ function chatIncludesConcierge(report: Partial>): boolean { - return Boolean(report?.participantAccountIDs?.length && report?.participantAccountIDs?.includes(CONST.ACCOUNT_ID.CONCIERGE)); + const participantAccountIDs = Object.keys(report?.participants ?? {}).map(Number); + return participantAccountIDs.includes(CONST.ACCOUNT_ID.CONCIERGE); } /** @@ -1513,24 +1515,32 @@ function getReportRecipientAccountIDs(report: OnyxEntry, currentLoginAcc } } - let finalParticipantAccountIDs: number[] | undefined = []; + let finalParticipantAccountIDs: number[] = []; if (isMoneyRequestReport(report)) { - // For money requests i.e the IOU (1:1 person) and Expense (1:* person) reports, use the full `initialParticipantAccountIDs` array - // and add the `ownerAccountId`. Money request reports don't add `ownerAccountId` in `participantAccountIDs` array - const defaultParticipantAccountIDs = finalReport?.participantAccountIDs ?? []; + // For money requests i.e the IOU (1:1 person) and Expense (1:* person) reports, use the full `participants` + // and add the `ownerAccountId`. Money request reports don't add `ownerAccountId` in `participants` array + const defaultParticipantAccountIDs = Object.keys(finalReport?.participants ?? {}).map(Number); const setOfParticipantAccountIDs = new Set(report?.ownerAccountID ? [...defaultParticipantAccountIDs, report.ownerAccountID] : defaultParticipantAccountIDs); finalParticipantAccountIDs = [...setOfParticipantAccountIDs]; } else if (isTaskReport(report)) { - // Task reports `managerID` will change when assignee is changed, in that case the old `managerID` is still present in `participantAccountIDs` - // array along with the new one. We only need the `managerID` as a participant here. + // Task reports `managerID` will change when assignee is changed, in that case the old `managerID` is still present in `participants` + // along with the new one. We only need the `managerID` as a participant here. finalParticipantAccountIDs = report?.managerID ? [report?.managerID] : []; } else { - finalParticipantAccountIDs = finalReport?.participantAccountIDs; + finalParticipantAccountIDs = Object.keys(finalReport?.participants ?? {}).map(Number); } - const reportParticipants = finalParticipantAccountIDs?.filter((accountID) => accountID !== currentLoginAccountID) ?? []; - const participantsWithoutExpensifyAccountIDs = reportParticipants.filter((participant) => !CONST.EXPENSIFY_ACCOUNT_IDS.includes(participant ?? 0)); - return participantsWithoutExpensifyAccountIDs; + const otherParticipantsWithoutExpensifyAccountIDs = finalParticipantAccountIDs.filter((accountID) => { + if (accountID === currentLoginAccountID) { + return false; + } + if (CONST.EXPENSIFY_ACCOUNT_IDS.includes(accountID)) { + return false; + } + return true; + }); + + return otherParticipantsWithoutExpensifyAccountIDs; } /** @@ -1935,7 +1945,9 @@ function getIcons( return [groupChatIcon]; } - return getIconsForParticipants(report?.participantAccountIDs ?? [], personalDetails); + const participantAccountIDs = Object.keys(report.participants ?? {}).map(Number); + + return getIconsForParticipants(participantAccountIDs, personalDetails); } function getDisplayNamesWithTooltips( @@ -3014,12 +3026,11 @@ function getReportName(report: OnyxEntry, policy: OnyxEntry = nu } // Not a room or PolicyExpenseChat, generate title from first 5 other participants - const participantAccountIDs = report?.participantAccountIDs?.slice(0, 6) ?? []; - const participantsWithoutCurrentUser = participantAccountIDs.filter((accountID) => accountID !== currentUserAccountID); + const participantsWithoutCurrentUser = Object.keys(report?.participants ?? {}) + .map(Number) + .filter((accountID) => accountID !== currentUserAccountID) + .slice(0, 5); const isMultipleParticipantReport = participantsWithoutCurrentUser.length > 1; - if (participantsWithoutCurrentUser.length > 5) { - participantsWithoutCurrentUser.pop(); - } return participantsWithoutCurrentUser.map((accountID) => getDisplayNameForParticipant(accountID, isMultipleParticipantReport)).join(', '); } @@ -3031,8 +3042,10 @@ function getPayeeName(report: OnyxEntry): string | undefined { return undefined; } - const participantAccountIDs = report?.participantAccountIDs ?? []; - const participantsWithoutCurrentUser = participantAccountIDs.filter((accountID) => accountID !== currentUserAccountID); + const participantsWithoutCurrentUser = Object.keys(report?.participants ?? {}) + .map(Number) + .filter((accountID) => accountID !== currentUserAccountID); + if (participantsWithoutCurrentUser.length === 0) { return undefined; } @@ -3089,15 +3102,14 @@ function getParentNavigationSubtitle(report: OnyxEntry): ParentNavigatio * Navigate to the details page of a given report */ function navigateToDetailsPage(report: OnyxEntry) { - const participantAccountIDs = report?.participantAccountIDs ?? []; - if (isSelfDM(report)) { Navigation.navigate(ROUTES.PROFILE.getRoute(currentUserAccountID ?? 0)); return; } if (isOneOnOneChat(report)) { - Navigation.navigate(ROUTES.PROFILE.getRoute(participantAccountIDs[0])); + const participantAccountID = Object.keys(report?.participants ?? {})[0]; + Navigation.navigate(ROUTES.PROFILE.getRoute(participantAccountID)); return; } if (report?.reportID) { @@ -3110,7 +3122,8 @@ function navigateToDetailsPage(report: OnyxEntry) { */ function goBackToDetailsPage(report: OnyxEntry) { if (isOneOnOneChat(report)) { - Navigation.goBack(ROUTES.PROFILE.getRoute(report?.participantAccountIDs?.[0] ?? '')); + const participantAccountID = Object.keys(report?.participants ?? {})[0]; + Navigation.goBack(ROUTES.PROFILE.getRoute(participantAccountID)); return; } Navigation.goBack(ROUTES.REPORT_SETTINGS.getRoute(report?.reportID ?? '')); @@ -3333,8 +3346,10 @@ function buildOptimisticIOUReport(payeeAccountID: number, payerAccountID: number const personalDetails = getPersonalDetailsForAccountID(payerAccountID); const payerEmail = 'login' in personalDetails ? personalDetails.login : ''; - // When creating a report the participantsAccountIDs and visibleChatMemberAccountIDs are the same - const participantsAccountIDs = [payeeAccountID, payerAccountID]; + const participants: Participants = { + [payeeAccountID]: {hidden: false}, + [payerAccountID]: {hidden: false}, + }; return { type: CONST.REPORT.TYPE.IOU, @@ -3343,8 +3358,7 @@ function buildOptimisticIOUReport(payeeAccountID: number, payerAccountID: number currency, managerID: payerAccountID, ownerAccountID: payeeAccountID, - participantAccountIDs: participantsAccountIDs, - visibleChatMemberAccountIDs: participantsAccountIDs, + participants, reportID: generateReportID(), stateNum: isSendingMoney ? CONST.REPORT.STATE_NUM.APPROVED : CONST.REPORT.STATE_NUM.SUBMITTED, statusNum: isSendingMoney ? CONST.REPORT.STATUS_NUM.REIMBURSED : CONST.REPORT.STATE_NUM.SUBMITTED, @@ -3962,10 +3976,6 @@ function buildOptimisticChatReport( ownerAccountID: ownerAccountID || CONST.REPORT.OWNER_ACCOUNT_ID_FAKE, parentReportActionID, parentReportID, - // When creating a report the participantsAccountIDs and visibleChatMemberAccountIDs are the same - participantAccountIDs: participantList, - visibleChatMemberAccountIDs: participantList, - // For group chats we need to have participants object as we are migrating away from `participantAccountIDs` and `visibleChatMemberAccountIDs`. See https://github.com/Expensify/App/issues/34692 participants, policyID, reportID: optimisticReportID || generateReportID(), @@ -4364,16 +4374,21 @@ function buildOptimisticTaskReport( description?: string, policyID: string = CONST.POLICY.OWNER_EMAIL_FAKE, ): OptimisticTaskReport { - // When creating a report the participantsAccountIDs and visibleChatMemberAccountIDs are the same - const participantsAccountIDs = assigneeAccountID && assigneeAccountID !== ownerAccountID ? [assigneeAccountID] : []; + const participants: Participants = + assigneeAccountID && assigneeAccountID !== ownerAccountID + ? { + [assigneeAccountID]: { + hidden: false, + }, + } + : {}; return { reportID: generateReportID(), reportName: title, description, ownerAccountID, - participantAccountIDs: participantsAccountIDs, - visibleChatMemberAccountIDs: participantsAccountIDs, + participants, managerID: assigneeAccountID, type: CONST.REPORT.TYPE.TASK, parentReportID, @@ -4535,7 +4550,7 @@ function canSeeDefaultRoom(report: OnyxEntry, policies: OnyxCollection

{ + const participantAccountIDs = Object.keys(report?.participants ?? {}); + // If the report has been deleted, or there are no participants (like an empty #admins room) then skip it if ( - !report || - report.participantAccountIDs?.length === 0 || + participantAccountIDs.length === 0 || isChatThread(report) || isTaskReport(report) || isMoneyRequestReport(report) || @@ -4761,8 +4779,10 @@ function getChatByParticipants(newParticipantList: number[], reports: OnyxCollec return false; } + const sortedParticipantsAccountIDs = participantAccountIDs.map(Number).sort(); + // Only return the chat if it has all the participants - return lodashIsEqual(sortedNewParticipantList, report.participantAccountIDs?.sort()); + return lodashIsEqual(sortedNewParticipantList, sortedParticipantsAccountIDs); }) ?? null ); } @@ -4774,11 +4794,15 @@ function getChatByParticipantsAndPolicy(newParticipantList: number[], policyID: newParticipantList.sort(); return ( Object.values(allReports ?? {}).find((report) => { + const participantAccountIDs = Object.keys(report?.participants ?? {}); + // If the report has been deleted, or there are no participants (like an empty #admins room) then skip it - if (!report?.participantAccountIDs) { + if (!report || participantAccountIDs.length === 0) { return false; } - const sortedParticipantsAccountIDs = report.participantAccountIDs?.sort(); + + const sortedParticipantsAccountIDs = participantAccountIDs.map(Number).sort(); + // Only return the room if it has all the participants and is not a policy room return report.policyID === policyID && newParticipantList.every((newParticipant) => sortedParticipantsAccountIDs.includes(newParticipant)); }) ?? null @@ -4793,7 +4817,8 @@ function getAllPolicyReports(policyID: string): Array> { * Returns true if Chronos is one of the chat participants (1:1) */ function chatIncludesChronos(report: OnyxEntry | EmptyObject): boolean { - return Boolean(report?.participantAccountIDs?.includes(CONST.ACCOUNT_ID.CHRONOS)); + const participantAccountIDs = Object.keys(report?.participants ?? {}).map(Number); + return participantAccountIDs.includes(CONST.ACCOUNT_ID.CHRONOS); } /** @@ -5257,7 +5282,7 @@ function getReportOfflinePendingActionAndErrors(report: OnyxEntry): Repo * Check if the report can create the expense with type is iouType */ function canCreateRequest(report: OnyxEntry, policy: OnyxEntry, iouType: (typeof CONST.IOU.TYPE)[keyof typeof CONST.IOU.TYPE]): boolean { - const participantAccountIDs = report?.participantAccountIDs ?? []; + const participantAccountIDs = Object.keys(report?.participants ?? {}).map(Number); if (!canUserPerformWriteAction(report)) { return false; } @@ -5515,7 +5540,7 @@ function isDeprecatedGroupDM(report: OnyxEntry): boolean { !isMoneyRequestReport(report) && !isArchivedRoom(report) && !Object.values(CONST.REPORT.CHAT_TYPE).some((chatType) => chatType === getChatType(report)) && - (report.participantAccountIDs?.length ?? 0) > 1, + Object.keys(report.participants ?? {}).length > 1, ); } @@ -5539,7 +5564,7 @@ function isReportParticipant(accountID: number, report: OnyxEntry): bool return true; } - const possibleAccountIDs = report?.participantAccountIDs ?? []; + const possibleAccountIDs = Object.keys(report?.participants ?? {}).map(Number); if (report?.ownerAccountID) { possibleAccountIDs.push(report?.ownerAccountID); } From bc24bfab0b5f1b755f4fbf9b52f86a6ba07fd450 Mon Sep 17 00:00:00 2001 From: Abdelhafidh Belalia <16493223+s77rt@users.noreply.github.com> Date: Sun, 21 Apr 2024 19:53:41 +0100 Subject: [PATCH 03/21] remove diff.tmp --- diff.tmp | 523 ------------------------------------------------------- 1 file changed, 523 deletions(-) delete mode 100644 diff.tmp diff --git a/diff.tmp b/diff.tmp deleted file mode 100644 index 286451f9e999..000000000000 --- a/diff.tmp +++ /dev/null @@ -1,523 +0,0 @@ -diff --git a/src/components/ReportWelcomeText.tsx b/src/components/ReportWelcomeText.tsx -index 219199c25b..3adc5feafa 100644 ---- a/src/components/ReportWelcomeText.tsx -+++ b/src/components/ReportWelcomeText.tsx -@@ -39,7 +39,7 @@ function ReportWelcomeText({report, policy, personalDetails}: ReportWelcomeTextP - const isChatRoom = ReportUtils.isChatRoom(report); - const isSelfDM = ReportUtils.isSelfDM(report); - const isDefault = !(isChatRoom || isPolicyExpenseChat || isSelfDM); -- const participantAccountIDs = report?.participantAccountIDs ?? []; -+ const participantAccountIDs = Object.keys(report?.participants ?? {}).map(Number); - const isMultipleParticipant = participantAccountIDs.length > 1; - const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips(OptionsListUtils.getPersonalDetailsForAccountIDs(participantAccountIDs, personalDetails), isMultipleParticipant); - const isUserPolicyAdmin = PolicyUtils.isPolicyAdmin(policy); -diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts -index 2aad4179c3..a229b74f7f 100644 ---- a/src/libs/OptionsListUtils.ts -+++ b/src/libs/OptionsListUtils.ts -@@ -476,14 +476,15 @@ function getSearchText( - const chatRoomSubtitle = ReportUtils.getChatRoomSubtitle(report); - - Array.prototype.push.apply(searchTerms, chatRoomSubtitle?.split(/[,\s]/) ?? ['']); -- } else { -- const visibleChatMemberAccountIDs = report.visibleChatMemberAccountIDs ?? []; -- if (allPersonalDetails) { -- for (const accountID of visibleChatMemberAccountIDs) { -- const login = allPersonalDetails[accountID]?.login; -- if (login) { -- searchTerms = searchTerms.concat(login); -- } -+ } else if (allPersonalDetails) { -+ const visibleParticipantAccountIDs = Object.entries(report.participants ?? {}) -+ .filter(([_, participant]) => participant && !participant.hidden) -+ .map(([accountID]) => Number(accountID)); -+ -+ for (const accountID of visibleParticipantAccountIDs) { -+ const login = allPersonalDetails[accountID]?.login; -+ if (login) { -+ searchTerms = searchTerms.concat(login); - } - } - } -@@ -708,11 +709,16 @@ function createOption( - result.isPinned = report.isPinned; - result.iouReportID = report.iouReportID; - result.keyForList = String(report.reportID); -- result.tooltipText = ReportUtils.getReportParticipantsTitle(report.visibleChatMemberAccountIDs ?? []); - result.isWaitingOnBankAccount = report.isWaitingOnBankAccount; - result.policyID = report.policyID; - result.isSelfDM = ReportUtils.isSelfDM(report); - -+ const visibleParticipantAccountIDs = Object.entries(report.participants ?? {}) -+ .filter(([_, participant]) => participant && !participant.hidden) -+ .map(([accountID]) => Number(accountID)); -+ -+ result.tooltipText = ReportUtils.getReportParticipantsTitle(visibleParticipantAccountIDs); -+ - hasMultipleParticipants = personalDetailList.length > 1 || result.isChatRoom || result.isPolicyExpenseChat; - subtitle = ReportUtils.getChatRoomSubtitle(report); - -@@ -776,8 +782,12 @@ function createOption( - function getReportOption(participant: Participant): ReportUtils.OptionData { - const report = ReportUtils.getReport(participant.reportID); - -+ const visibleParticipantAccountIDs = Object.entries(report?.participants ?? {}) -+ .filter(([_, participant]) => participant && !participant.hidden) -+ .map(([accountID]) => Number(accountID)); -+ - const option = createOption( -- report?.visibleChatMemberAccountIDs ?? [], -+ visibleParticipantAccountIDs, - allPersonalDetails ?? {}, - !isEmptyObject(report) ? report : null, - {}, -@@ -805,8 +815,12 @@ function getReportOption(participant: Participant): ReportUtils.OptionData { - function getPolicyExpenseReportOption(participant: Participant | ReportUtils.OptionData): ReportUtils.OptionData { - const expenseReport = ReportUtils.isPolicyExpenseChat(participant) ? ReportUtils.getReport(participant.reportID) : null; - -+ const visibleParticipantAccountIDs = Object.entries(expenseReport?.participants ?? {}) -+ .filter(([_, participant]) => participant && !participant.hidden) -+ .map(([accountID]) => Number(accountID)); -+ - const option = createOption( -- expenseReport?.visibleChatMemberAccountIDs ?? [], -+ visibleParticipantAccountIDs, - allPersonalDetails ?? {}, - !isEmptyObject(expenseReport) ? expenseReport : null, - {}, -@@ -1472,8 +1486,12 @@ function createOptionList(personalDetails: OnyxEntry, repor - } - - const isSelfDM = ReportUtils.isSelfDM(report); -- // Currently, currentUser is not included in visibleChatMemberAccountIDs, so for selfDM we need to add the currentUser as participants. -- const accountIDs = isSelfDM ? [currentUserAccountID ?? 0] : report.visibleChatMemberAccountIDs ?? []; -+ const visibleParticipantAccountIDs = Object.entries(report.participants ?? {}) -+ .filter(([_, participant]) => participant && !participant.hidden) -+ .map(([accountID]) => Number(accountID)); -+ -+ // Currently, currentUser is not included in visibleParticipantAccountIDs, so for selfDM we need to add the currentUser as participants. -+ const accountIDs = isSelfDM ? [currentUserAccountID ?? 0] : visibleParticipantAccountIDs ?? []; - - if (!accountIDs || accountIDs.length === 0) { - return; -@@ -1505,7 +1523,7 @@ function createOptionList(personalDetails: OnyxEntry, repor - } - - function createOptionFromReport(report: Report, personalDetails: OnyxEntry) { -- const accountIDs = report.participantAccountIDs ?? []; -+ const accountIDs = Object.keys(report.participants ?? {}).map(Number); - - return { - item: report, -@@ -1692,8 +1710,12 @@ function getOptions( - const isPolicyExpenseChat = option.isPolicyExpenseChat; - const isMoneyRequestReport = option.isMoneyRequestReport; - const isSelfDM = option.isSelfDM; -- // Currently, currentUser is not included in visibleChatMemberAccountIDs, so for selfDM we need to add the currentUser as participants. -- const accountIDs = isSelfDM ? [currentUserAccountID ?? 0] : report.visibleChatMemberAccountIDs ?? []; -+ const visibleParticipantAccountIDs = Object.entries(report.participants ?? {}) -+ .filter(([_, participant]) => participant && !participant.hidden) -+ .map(([accountID]) => Number(accountID)); -+ -+ // Currently, currentUser is not included in visibleParticipantAccountIDs, so for selfDM we need to add the currentUser as participants. -+ const accountIDs = isSelfDM ? [currentUserAccountID ?? 0] : visibleParticipantAccountIDs ?? []; - - if (isPolicyExpenseChat && report.isOwnPolicyExpenseChat && !includeOwnedWorkspaceChats) { - return; -diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts -index af55b4ca29..d8c2c08e65 100644 ---- a/src/libs/ReportUtils.ts -+++ b/src/libs/ReportUtils.ts -@@ -272,8 +272,6 @@ type OptimisticChatReport = Pick< - | 'parentReportActionID' - | 'parentReportID' - | 'participants' -- | 'participantAccountIDs' -- | 'visibleChatMemberAccountIDs' - | 'policyID' - | 'reportID' - | 'reportName' -@@ -333,8 +331,7 @@ type OptimisticTaskReport = Pick< - | 'reportName' - | 'description' - | 'ownerAccountID' -- | 'participantAccountIDs' -- | 'visibleChatMemberAccountIDs' -+ | 'participants' - | 'managerID' - | 'type' - | 'parentReportID' -@@ -373,8 +370,7 @@ type OptimisticIOUReport = Pick< - | 'managerID' - | 'policyID' - | 'ownerAccountID' -- | 'participantAccountIDs' -- | 'visibleChatMemberAccountIDs' -+ | 'participants' - | 'reportID' - | 'stateNum' - | 'statusNum' -@@ -976,7 +972,8 @@ function isGroupChat(report: OnyxEntry | Partial): boolean { - * Only returns true if this is our main 1:1 DM report with Concierge - */ - function isConciergeChatReport(report: OnyxEntry): boolean { -- return report?.participantAccountIDs?.length === 1 && Number(report.participantAccountIDs?.[0]) === CONST.ACCOUNT_ID.CONCIERGE && !isChatThread(report); -+ const participantAccountIDs = Object.keys(report?.participants ?? {}).map(Number); -+ return participantAccountIDs.length === 1 && participantAccountIDs[0] === CONST.ACCOUNT_ID.CONCIERGE && !isChatThread(report); - } - - function findSelfDMReportID(): string | undefined { -@@ -1032,7 +1029,7 @@ function isProcessingReport(report: OnyxEntry | EmptyObject): boolean { - * and personal detail of participant is optimistic data - */ - function shouldDisableDetailPage(report: OnyxEntry): boolean { -- const participantAccountIDs = report?.participantAccountIDs ?? []; -+ const participantAccountIDs = Object.keys(report?.participants ?? {}).map(Number); - - if (isChatRoom(report) || isPolicyExpenseChat(report) || isChatThread(report) || isTaskReport(report)) { - return false; -@@ -1047,8 +1044,10 @@ function shouldDisableDetailPage(report: OnyxEntry): boolean { - * Returns true if this report has only one participant and it's an Expensify account. - */ - function isExpensifyOnlyParticipantInReport(report: OnyxEntry): boolean { -- const reportParticipants = report?.participantAccountIDs?.filter((accountID) => accountID !== currentUserAccountID) ?? []; -- return reportParticipants.length === 1 && reportParticipants.some((accountID) => CONST.EXPENSIFY_ACCOUNT_IDS.includes(accountID)); -+ const otherParticipants = Object.keys(report?.participants ?? {}) -+ .map(Number) -+ .filter((accountID) => accountID !== currentUserAccountID); -+ return otherParticipants.length === 1 && otherParticipants.some((accountID) => CONST.EXPENSIFY_ACCOUNT_IDS.includes(accountID)); - } - - /** -@@ -1057,8 +1056,10 @@ function isExpensifyOnlyParticipantInReport(report: OnyxEntry): boolean - * - */ - function canCreateTaskInReport(report: OnyxEntry): boolean { -- const otherReportParticipants = report?.participantAccountIDs?.filter((accountID) => accountID !== currentUserAccountID) ?? []; -- const areExpensifyAccountsOnlyOtherParticipants = otherReportParticipants?.length >= 1 && otherReportParticipants?.every((accountID) => CONST.EXPENSIFY_ACCOUNT_IDS.includes(accountID)); -+ const otherParticipants = Object.keys(report?.participants ?? {}) -+ .map(Number) -+ .filter((accountID) => accountID !== currentUserAccountID); -+ const areExpensifyAccountsOnlyOtherParticipants = otherParticipants.length >= 1 && otherParticipants.every((accountID) => CONST.EXPENSIFY_ACCOUNT_IDS.includes(accountID)); - if (areExpensifyAccountsOnlyOtherParticipants && isDM(report)) { - return false; - } -@@ -1112,7 +1113,7 @@ function findLastAccessedReport( - // Check where ReportUtils.findLastAccessedReport is called in MainDrawerNavigator.js for more context. - // Domain rooms are now the only type of default room that are on the defaultRooms beta. - sortedReports = sortedReports.filter( -- (report) => !isDomainRoom(report) || getPolicyType(report, policies) === CONST.POLICY.TYPE.FREE || hasExpensifyGuidesEmails(report?.participantAccountIDs ?? []), -+ (report) => !isDomainRoom(report) || getPolicyType(report, policies) === CONST.POLICY.TYPE.FREE || hasExpensifyGuidesEmails(Object.keys(report?.participants ?? {}).map(Number)), - ); - } - -@@ -1215,7 +1216,7 @@ function isPolicyAdmin(policyID: string, policies: OnyxCollection): bool - * Returns true if report has a single participant. - */ - function hasSingleParticipant(report: OnyxEntry): boolean { -- return report?.participantAccountIDs?.length === 1; -+ return Object.keys(report?.participants ?? {}).length === 1; - } - - /** -@@ -1330,7 +1331,7 @@ function isOneTransactionThread(reportID: string, parentReportID: string): boole - * - */ - function isOneOnOneChat(report: OnyxEntry): boolean { -- const participantAccountIDs = report?.participantAccountIDs ?? []; -+ const participantAccountIDs = Object.keys(report?.participants ?? {}); - return ( - !isChatRoom(report) && - !isExpenseRequest(report) && -@@ -1492,7 +1493,8 @@ function getRoomWelcomeMessage(report: OnyxEntry, isUserPolicyAdmin: boo - * Returns true if Concierge is one of the chat participants (1:1 as well as group chats) - */ - function chatIncludesConcierge(report: Partial>): boolean { -- return Boolean(report?.participantAccountIDs?.length && report?.participantAccountIDs?.includes(CONST.ACCOUNT_ID.CONCIERGE)); -+ const participantAccountIDs = Object.keys(report?.participants ?? {}).map(Number); -+ return participantAccountIDs.includes(CONST.ACCOUNT_ID.CONCIERGE); - } - - /** -@@ -1513,24 +1515,32 @@ function getReportRecipientAccountIDs(report: OnyxEntry, currentLoginAcc - } - } - -- let finalParticipantAccountIDs: number[] | undefined = []; -+ let finalParticipantAccountIDs: number[] = []; - if (isMoneyRequestReport(report)) { -- // For money requests i.e the IOU (1:1 person) and Expense (1:* person) reports, use the full `initialParticipantAccountIDs` array -- // and add the `ownerAccountId`. Money request reports don't add `ownerAccountId` in `participantAccountIDs` array -- const defaultParticipantAccountIDs = finalReport?.participantAccountIDs ?? []; -+ // For money requests i.e the IOU (1:1 person) and Expense (1:* person) reports, use the full `participants` -+ // and add the `ownerAccountId`. Money request reports don't add `ownerAccountId` in `participants` array -+ const defaultParticipantAccountIDs = Object.keys(finalReport?.participants ?? {}).map(Number); - const setOfParticipantAccountIDs = new Set(report?.ownerAccountID ? [...defaultParticipantAccountIDs, report.ownerAccountID] : defaultParticipantAccountIDs); - finalParticipantAccountIDs = [...setOfParticipantAccountIDs]; - } else if (isTaskReport(report)) { -- // Task reports `managerID` will change when assignee is changed, in that case the old `managerID` is still present in `participantAccountIDs` -- // array along with the new one. We only need the `managerID` as a participant here. -+ // Task reports `managerID` will change when assignee is changed, in that case the old `managerID` is still present in `participants` -+ // along with the new one. We only need the `managerID` as a participant here. - finalParticipantAccountIDs = report?.managerID ? [report?.managerID] : []; - } else { -- finalParticipantAccountIDs = finalReport?.participantAccountIDs; -+ finalParticipantAccountIDs = Object.keys(finalReport?.participants ?? {}).map(Number); - } - -- const reportParticipants = finalParticipantAccountIDs?.filter((accountID) => accountID !== currentLoginAccountID) ?? []; -- const participantsWithoutExpensifyAccountIDs = reportParticipants.filter((participant) => !CONST.EXPENSIFY_ACCOUNT_IDS.includes(participant ?? 0)); -- return participantsWithoutExpensifyAccountIDs; -+ const otherParticipantsWithoutExpensifyAccountIDs = finalParticipantAccountIDs.filter((accountID) => { -+ if (accountID === currentLoginAccountID) { -+ return false; -+ } -+ if (CONST.EXPENSIFY_ACCOUNT_IDS.includes(accountID)) { -+ return false; -+ } -+ return true; -+ }); -+ -+ return otherParticipantsWithoutExpensifyAccountIDs; - } - - /** -@@ -1935,7 +1945,9 @@ function getIcons( - return [groupChatIcon]; - } - -- return getIconsForParticipants(report?.participantAccountIDs ?? [], personalDetails); -+ const participantAccountIDs = Object.keys(report.participants ?? {}).map(Number); -+ -+ return getIconsForParticipants(participantAccountIDs, personalDetails); - } - - function getDisplayNamesWithTooltips( -@@ -3014,12 +3026,11 @@ function getReportName(report: OnyxEntry, policy: OnyxEntry = nu - } - - // Not a room or PolicyExpenseChat, generate title from first 5 other participants -- const participantAccountIDs = report?.participantAccountIDs?.slice(0, 6) ?? []; -- const participantsWithoutCurrentUser = participantAccountIDs.filter((accountID) => accountID !== currentUserAccountID); -+ const participantsWithoutCurrentUser = Object.keys(report?.participants ?? {}) -+ .map(Number) -+ .filter((accountID) => accountID !== currentUserAccountID) -+ .slice(0, 5); - const isMultipleParticipantReport = participantsWithoutCurrentUser.length > 1; -- if (participantsWithoutCurrentUser.length > 5) { -- participantsWithoutCurrentUser.pop(); -- } - return participantsWithoutCurrentUser.map((accountID) => getDisplayNameForParticipant(accountID, isMultipleParticipantReport)).join(', '); - } - -@@ -3031,8 +3042,10 @@ function getPayeeName(report: OnyxEntry): string | undefined { - return undefined; - } - -- const participantAccountIDs = report?.participantAccountIDs ?? []; -- const participantsWithoutCurrentUser = participantAccountIDs.filter((accountID) => accountID !== currentUserAccountID); -+ const participantsWithoutCurrentUser = Object.keys(report?.participants ?? {}) -+ .map(Number) -+ .filter((accountID) => accountID !== currentUserAccountID); -+ - if (participantsWithoutCurrentUser.length === 0) { - return undefined; - } -@@ -3089,15 +3102,14 @@ function getParentNavigationSubtitle(report: OnyxEntry): ParentNavigatio - * Navigate to the details page of a given report - */ - function navigateToDetailsPage(report: OnyxEntry) { -- const participantAccountIDs = report?.participantAccountIDs ?? []; -- - if (isSelfDM(report)) { - Navigation.navigate(ROUTES.PROFILE.getRoute(currentUserAccountID ?? 0)); - return; - } - - if (isOneOnOneChat(report)) { -- Navigation.navigate(ROUTES.PROFILE.getRoute(participantAccountIDs[0])); -+ const participantAccountID = Object.keys(report?.participants ?? {})[0]; -+ Navigation.navigate(ROUTES.PROFILE.getRoute(participantAccountID)); - return; - } - if (report?.reportID) { -@@ -3110,7 +3122,8 @@ function navigateToDetailsPage(report: OnyxEntry) { - */ - function goBackToDetailsPage(report: OnyxEntry) { - if (isOneOnOneChat(report)) { -- Navigation.goBack(ROUTES.PROFILE.getRoute(report?.participantAccountIDs?.[0] ?? '')); -+ const participantAccountID = Object.keys(report?.participants ?? {})[0]; -+ Navigation.goBack(ROUTES.PROFILE.getRoute(participantAccountID)); - return; - } - Navigation.goBack(ROUTES.REPORT_SETTINGS.getRoute(report?.reportID ?? '')); -@@ -3333,8 +3346,10 @@ function buildOptimisticIOUReport(payeeAccountID: number, payerAccountID: number - const personalDetails = getPersonalDetailsForAccountID(payerAccountID); - const payerEmail = 'login' in personalDetails ? personalDetails.login : ''; - -- // When creating a report the participantsAccountIDs and visibleChatMemberAccountIDs are the same -- const participantsAccountIDs = [payeeAccountID, payerAccountID]; -+ const participants: Participants = { -+ [payeeAccountID]: {hidden: false}, -+ [payerAccountID]: {hidden: false}, -+ }; - - return { - type: CONST.REPORT.TYPE.IOU, -@@ -3343,8 +3358,7 @@ function buildOptimisticIOUReport(payeeAccountID: number, payerAccountID: number - currency, - managerID: payerAccountID, - ownerAccountID: payeeAccountID, -- participantAccountIDs: participantsAccountIDs, -- visibleChatMemberAccountIDs: participantsAccountIDs, -+ participants, - reportID: generateReportID(), - stateNum: isSendingMoney ? CONST.REPORT.STATE_NUM.APPROVED : CONST.REPORT.STATE_NUM.SUBMITTED, - statusNum: isSendingMoney ? CONST.REPORT.STATUS_NUM.REIMBURSED : CONST.REPORT.STATE_NUM.SUBMITTED, -@@ -3962,10 +3976,6 @@ function buildOptimisticChatReport( - ownerAccountID: ownerAccountID || CONST.REPORT.OWNER_ACCOUNT_ID_FAKE, - parentReportActionID, - parentReportID, -- // When creating a report the participantsAccountIDs and visibleChatMemberAccountIDs are the same -- participantAccountIDs: participantList, -- visibleChatMemberAccountIDs: participantList, -- // For group chats we need to have participants object as we are migrating away from `participantAccountIDs` and `visibleChatMemberAccountIDs`. See https://github.com/Expensify/App/issues/34692 - participants, - policyID, - reportID: optimisticReportID || generateReportID(), -@@ -4364,16 +4374,21 @@ function buildOptimisticTaskReport( - description?: string, - policyID: string = CONST.POLICY.OWNER_EMAIL_FAKE, - ): OptimisticTaskReport { -- // When creating a report the participantsAccountIDs and visibleChatMemberAccountIDs are the same -- const participantsAccountIDs = assigneeAccountID && assigneeAccountID !== ownerAccountID ? [assigneeAccountID] : []; -+ const participants: Participants = -+ assigneeAccountID && assigneeAccountID !== ownerAccountID -+ ? { -+ [assigneeAccountID]: { -+ hidden: false, -+ }, -+ } -+ : {}; - - return { - reportID: generateReportID(), - reportName: title, - description, - ownerAccountID, -- participantAccountIDs: participantsAccountIDs, -- visibleChatMemberAccountIDs: participantsAccountIDs, -+ participants, - managerID: assigneeAccountID, - type: CONST.REPORT.TYPE.TASK, - parentReportID, -@@ -4535,7 +4550,7 @@ function canSeeDefaultRoom(report: OnyxEntry, policies: OnyxCollection

{ -+ const participantAccountIDs = Object.keys(report?.participants ?? {}); -+ - // If the report has been deleted, or there are no participants (like an empty #admins room) then skip it - if ( -- !report || -- report.participantAccountIDs?.length === 0 || -+ participantAccountIDs.length === 0 || - isChatThread(report) || - isTaskReport(report) || - isMoneyRequestReport(report) || -@@ -4761,8 +4779,10 @@ function getChatByParticipants(newParticipantList: number[], reports: OnyxCollec - return false; - } - -+ const sortedParticipantsAccountIDs = participantAccountIDs.map(Number).sort(); -+ - // Only return the chat if it has all the participants -- return lodashIsEqual(sortedNewParticipantList, report.participantAccountIDs?.sort()); -+ return lodashIsEqual(sortedNewParticipantList, sortedParticipantsAccountIDs); - }) ?? null - ); - } -@@ -4774,11 +4794,15 @@ function getChatByParticipantsAndPolicy(newParticipantList: number[], policyID: - newParticipantList.sort(); - return ( - Object.values(allReports ?? {}).find((report) => { -+ const participantAccountIDs = Object.keys(report?.participants ?? {}); -+ - // If the report has been deleted, or there are no participants (like an empty #admins room) then skip it -- if (!report?.participantAccountIDs) { -+ if (!report || participantAccountIDs.length === 0) { - return false; - } -- const sortedParticipantsAccountIDs = report.participantAccountIDs?.sort(); -+ -+ const sortedParticipantsAccountIDs = participantAccountIDs.map(Number).sort(); -+ - // Only return the room if it has all the participants and is not a policy room - return report.policyID === policyID && newParticipantList.every((newParticipant) => sortedParticipantsAccountIDs.includes(newParticipant)); - }) ?? null -@@ -4793,7 +4817,8 @@ function getAllPolicyReports(policyID: string): Array> { - * Returns true if Chronos is one of the chat participants (1:1) - */ - function chatIncludesChronos(report: OnyxEntry | EmptyObject): boolean { -- return Boolean(report?.participantAccountIDs?.includes(CONST.ACCOUNT_ID.CHRONOS)); -+ const participantAccountIDs = Object.keys(report?.participants ?? {}).map(Number); -+ return participantAccountIDs.includes(CONST.ACCOUNT_ID.CHRONOS); - } - - /** -@@ -5257,7 +5282,7 @@ function getReportOfflinePendingActionAndErrors(report: OnyxEntry): Repo - * Check if the report can create the expense with type is iouType - */ - function canCreateRequest(report: OnyxEntry, policy: OnyxEntry, iouType: (typeof CONST.IOU.TYPE)[keyof typeof CONST.IOU.TYPE]): boolean { -- const participantAccountIDs = report?.participantAccountIDs ?? []; -+ const participantAccountIDs = Object.keys(report?.participants ?? {}).map(Number); - if (!canUserPerformWriteAction(report)) { - return false; - } -@@ -5515,7 +5540,7 @@ function isDeprecatedGroupDM(report: OnyxEntry): boolean { - !isMoneyRequestReport(report) && - !isArchivedRoom(report) && - !Object.values(CONST.REPORT.CHAT_TYPE).some((chatType) => chatType === getChatType(report)) && -- (report.participantAccountIDs?.length ?? 0) > 1, -+ Object.keys(report.participants ?? {}).length > 1, - ); - } - -@@ -5539,7 +5564,7 @@ function isReportParticipant(accountID: number, report: OnyxEntry): bool - return true; - } - -- const possibleAccountIDs = report?.participantAccountIDs ?? []; -+ const possibleAccountIDs = Object.keys(report?.participants ?? {}).map(Number); - if (report?.ownerAccountID) { - possibleAccountIDs.push(report?.ownerAccountID); - } From ea566257f59bae9f32ffc35d7145039ec48c01c9 Mon Sep 17 00:00:00 2001 From: Abdelhafidh Belalia <16493223+s77rt@users.noreply.github.com> Date: Wed, 24 Apr 2024 14:49:21 +0100 Subject: [PATCH 04/21] Participants migration: SidebarUtils, IOU, Task --- src/libs/SidebarUtils.ts | 9 +++++++-- src/libs/actions/IOU.ts | 5 ++++- src/libs/actions/Task.ts | 6 +++--- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index b1cdde61d6cd..338b01a51309 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -235,7 +235,7 @@ function getOptionData({ isDeletedParentAction: false, }; - let participantAccountIDs = report.participantAccountIDs ?? []; + let participantAccountIDs = Object.keys(report.participants ?? {}).map(Number); // Currently, currentUser is not included in participantAccountIDs, so for selfDM we need to add the currentUser(report owner) as participants. if (ReportUtils.isSelfDM(report)) { @@ -270,7 +270,6 @@ function getOptionData({ result.isPinned = report.isPinned; result.iouReportID = report.iouReportID; result.keyForList = String(report.reportID); - result.tooltipText = ReportUtils.getReportParticipantsTitle(report.visibleChatMemberAccountIDs ?? []); result.hasOutstandingChildRequest = report.hasOutstandingChildRequest; result.parentReportID = report.parentReportID ?? ''; result.isWaitingOnBankAccount = report.isWaitingOnBankAccount; @@ -280,6 +279,12 @@ function getOptionData({ result.isDeletedParentAction = report.isDeletedParentAction; result.isSelfDM = ReportUtils.isSelfDM(report); + const visibleParticipantAccountIDs = Object.entries(report.participants ?? {}) + .filter(([_, participant]) => participant && !participant.hidden) + .map(([accountID]) => Number(accountID)); + + result.tooltipText = ReportUtils.getReportParticipantsTitle(visibleParticipantAccountIDs); + const hasMultipleParticipants = participantPersonalDetailList.length > 1 || result.isChatRoom || result.isPolicyExpenseChat || ReportUtils.isExpenseReport(report); const subtitle = ReportUtils.getChatRoomSubtitle(report); diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 7fb31cbed647..fc65dcb01712 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -5810,10 +5810,13 @@ function setMoneyRequestParticipantsFromReport(transactionID: string, report: On const chatReport = ReportUtils.isMoneyRequestReport(report) ? ReportUtils.getReport(report?.chatReportID) : report; const currentUserAccountID = currentUserPersonalDetails.accountID; const shouldAddAsReport = !isEmptyObject(chatReport) && ReportUtils.isSelfDM(chatReport); + const chatReportOtherParticipants = Object.keys(chatReport?.participants ?? {}) + .map(Number) + .filter((accountID) => accountID !== currentUserAccountID); const participants: Participant[] = ReportUtils.isPolicyExpenseChat(chatReport) || shouldAddAsReport ? [{accountID: 0, reportID: chatReport?.reportID, isPolicyExpenseChat: ReportUtils.isPolicyExpenseChat(chatReport), selected: true}] - : (chatReport?.participantAccountIDs ?? []).filter((accountID) => currentUserAccountID !== accountID).map((accountID) => ({accountID, selected: true})); + : chatReportOtherParticipants.map((accountID) => ({accountID, selected: true})); Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, {participants, participantsAutoAssigned: true}); diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index 752cd9df0325..8c0dd2e85f62 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -25,7 +25,7 @@ import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type {EmptyObject} from '@src/types/utils/EmptyObject'; import * as Report from './Report'; -type OptimisticReport = Pick; +type OptimisticReport = Pick; type Assignee = { icons: Icon[]; displayName: string; @@ -766,13 +766,13 @@ function getAssignee(assigneeAccountID: number, personalDetails: OnyxEntry, personalDetails: OnyxEntry): ShareDestination { const report = reports?.[`report_${reportID}`] ?? null; - const participantAccountIDs = report?.participantAccountIDs ?? []; + const participantAccountIDs = Object.keys(report?.participants ?? {}).map(Number); const isMultipleParticipant = participantAccountIDs.length > 1; const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips(OptionsListUtils.getPersonalDetailsForAccountIDs(participantAccountIDs, personalDetails), isMultipleParticipant); let subtitle = ''; if (ReportUtils.isChatReport(report) && ReportUtils.isDM(report) && ReportUtils.hasSingleParticipant(report)) { - const participantAccountID = report?.participantAccountIDs?.[0] ?? -1; + const participantAccountID = participantAccountIDs[0] ?? -1; const displayName = personalDetails?.[participantAccountID]?.displayName ?? ''; const login = personalDetails?.[participantAccountID]?.login ?? ''; From bfba18d0d969e2a07232a2c49b18a4d7972bfe06 Mon Sep 17 00:00:00 2001 From: Abdelhafidh Belalia <16493223+s77rt@users.noreply.github.com> Date: Sun, 28 Apr 2024 20:07:38 +0100 Subject: [PATCH 05/21] Participants migration: Task, Report, Policy, HeaderView, ProfilePage, ReportScreen, ReportActionCompose --- src/libs/actions/Policy.ts | 103 ++++++++---------- src/libs/actions/Report.ts | 40 +------ src/libs/actions/Task.ts | 7 +- src/pages/ProfilePage.tsx | 2 +- src/pages/home/HeaderView.tsx | 6 +- src/pages/home/ReportScreen.tsx | 4 +- .../ReportActionCompose.tsx | 10 +- 7 files changed, 66 insertions(+), 106 deletions(-) diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 6d457f150fe6..5a463e634e3c 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -447,30 +447,23 @@ function buildAnnounceRoomMembersOnyxData(policyID: string, accountIDs: number[] return announceRoomMembers; } - if (announceReport?.participantAccountIDs) { - // Everyone in special policy rooms is visible - const participantAccountIDs = [...announceReport.participantAccountIDs, ...accountIDs]; - const pendingChatMembers = ReportUtils.getPendingChatMembers(accountIDs, announceReport?.pendingChatMembers ?? [], CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD); + const participantAccountIDs = [...Object.keys(announceReport.participants ?? {}).map(Number), ...accountIDs]; + const pendingChatMembers = ReportUtils.getPendingChatMembers(accountIDs, announceReport?.pendingChatMembers ?? [], CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD); - announceRoomMembers.onyxOptimisticData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${announceReport?.reportID}`, - value: { - participants: ReportUtils.buildParticipantsFromAccountIDs(participantAccountIDs), - participantAccountIDs, - visibleChatMemberAccountIDs: participantAccountIDs, - pendingChatMembers, - }, - }); - } + announceRoomMembers.onyxOptimisticData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${announceReport?.reportID}`, + value: { + participants: ReportUtils.buildParticipantsFromAccountIDs(participantAccountIDs), + pendingChatMembers, + }, + }); announceRoomMembers.onyxFailureData.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${announceReport?.reportID}`, value: { - participants: announceReport?.participants, - participantAccountIDs: announceReport?.participantAccountIDs, - visibleChatMemberAccountIDs: announceReport?.visibleChatMemberAccountIDs, + participants: announceReport?.participants ?? null, pendingChatMembers: announceReport?.pendingChatMembers ?? null, }, }); @@ -781,45 +774,43 @@ function removeOptimisticAnnounceRoomMembers(policyID: string, policyName: strin return announceRoomMembers; } - if (announceReport?.participantAccountIDs) { - const pendingChatMembers = ReportUtils.getPendingChatMembers(accountIDs, announceReport?.pendingChatMembers ?? [], CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE); + const pendingChatMembers = ReportUtils.getPendingChatMembers(accountIDs, announceReport?.pendingChatMembers ?? [], CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE); - announceRoomMembers.onyxOptimisticData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${announceReport.reportID}`, - value: { - pendingChatMembers, - ...(accountIDs.includes(sessionAccountID) - ? { - statusNum: CONST.REPORT.STATUS_NUM.CLOSED, - stateNum: CONST.REPORT.STATE_NUM.APPROVED, - oldPolicyName: policyName, - } - : {}), - }, - }); - announceRoomMembers.onyxFailureData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${announceReport.reportID}`, - value: { - pendingChatMembers: announceReport?.pendingChatMembers ?? null, - ...(accountIDs.includes(sessionAccountID) - ? { - statusNum: announceReport.statusNum, - stateNum: announceReport.stateNum, - oldPolicyName: announceReport.oldPolicyName, - } - : {}), - }, - }); - announceRoomMembers.onyxSuccessData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${announceReport.reportID}`, - value: { - pendingChatMembers: announceReport?.pendingChatMembers ?? null, - }, - }); - } + announceRoomMembers.onyxOptimisticData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${announceReport.reportID}`, + value: { + pendingChatMembers, + ...(accountIDs.includes(sessionAccountID) + ? { + statusNum: CONST.REPORT.STATUS_NUM.CLOSED, + stateNum: CONST.REPORT.STATE_NUM.APPROVED, + oldPolicyName: policyName, + } + : {}), + }, + }); + announceRoomMembers.onyxFailureData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${announceReport.reportID}`, + value: { + pendingChatMembers: announceReport?.pendingChatMembers ?? null, + ...(accountIDs.includes(sessionAccountID) + ? { + statusNum: announceReport.statusNum, + stateNum: announceReport.stateNum, + oldPolicyName: announceReport.oldPolicyName, + } + : {}), + }, + }); + announceRoomMembers.onyxSuccessData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${announceReport.reportID}`, + value: { + pendingChatMembers: announceReport?.pendingChatMembers ?? null, + }, + }); return announceRoomMembers; } diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 6fc51cd6b066..2d60a899b66d 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -509,7 +509,7 @@ function addActions(reportID: string, text = '', file?: FileObject) { clientCreatedTime: file ? attachmentAction?.created : reportCommentAction?.created, }; - if (reportIDDeeplinkedFromOldDot === reportID && report?.participantAccountIDs?.length === 1 && Number(report.participantAccountIDs?.[0]) === CONST.ACCOUNT_ID.CONCIERGE) { + if (reportIDDeeplinkedFromOldDot === reportID && ReportUtils.isConciergeChatReport(report)) { parameters.isOldDotConciergeChat = true; } @@ -811,12 +811,9 @@ function openReport( // Add optimistic personal details for new participants const optimisticPersonalDetails: OnyxCollection = {}; const settledPersonalDetails: OnyxCollection = {}; + const participantAccountIDs = PersonalDetailsUtils.getAccountIDsByLogins(participantLoginList); participantLoginList.forEach((login, index) => { - const accountID = newReportObject?.participantAccountIDs?.[index]; - - if (!accountID) { - return; - } + const accountID = participantAccountIDs[index]; optimisticPersonalDetails[accountID] = allPersonalDetails?.[accountID] ?? { login, @@ -968,7 +965,7 @@ function navigateToAndOpenChildReport(childReportID = '0', parentReportAction: P parentReportID, ); - const participantLogins = PersonalDetailsUtils.getLoginsByAccountIDs(newChat?.participantAccountIDs ?? []); + const participantLogins = PersonalDetailsUtils.getLoginsByAccountIDs(Object.keys(newChat.participants ?? {}).map(Number)); openReport(newChat.reportID, '', participantLogins, newChat, parentReportAction.reportActionID); Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(newChat.reportID)); } @@ -2608,12 +2605,6 @@ function inviteToRoom(reportID: string, inviteeEmailsToAccountIDs: InvitedEmails const inviteeEmails = Object.keys(inviteeEmailsToAccountIDs); const inviteeAccountIDs = Object.values(inviteeEmailsToAccountIDs); - const participantAccountIDsAfterInvitation = [...new Set([...(report?.participantAccountIDs ?? []), ...inviteeAccountIDs])].filter( - (accountID): accountID is number => typeof accountID === 'number', - ); - const visibleMemberAccountIDsAfterInvitation = [...new Set([...(report?.visibleChatMemberAccountIDs ?? []), ...inviteeAccountIDs])].filter( - (accountID): accountID is number => typeof accountID === 'number', - ); const participantsAfterInvitation = inviteeAccountIDs.reduce( (reportParticipants: Participants, accountID: number) => { @@ -2638,8 +2629,6 @@ function inviteToRoom(reportID: string, inviteeEmailsToAccountIDs: InvitedEmails onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, value: { - participantAccountIDs: participantAccountIDsAfterInvitation, - visibleChatMemberAccountIDs: visibleMemberAccountIDsAfterInvitation, participants: participantsAfterInvitation, pendingChatMembers, }, @@ -2700,7 +2689,6 @@ function clearAddRoomMemberError(reportID: string, invitedAccountID: string) { const report = currentReportData?.[reportID]; Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, { pendingChatMembers: report?.pendingChatMembers?.filter((pendingChatMember) => pendingChatMember.accountID !== invitedAccountID), - participantAccountIDs: report?.parentReportActionIDs?.filter((parentReportActionID) => parentReportActionID !== Number(invitedAccountID)), participants: { [invitedAccountID]: null, }, @@ -2746,24 +2734,6 @@ function removeFromRoom(reportID: string, targetAccountIDs: number[]) { } const removeParticipantsData: Record = {}; - const currentParticipants = ReportUtils.getParticipants(reportID); - if (!currentParticipants) { - return; - } - - const currentParticipantAccountIDs = ReportUtils.getParticipantAccountIDs(reportID); - const participantAccountIDsAfterRemoval: number[] = []; - const visibleChatMemberAccountIDsAfterRemoval: number[] = []; - currentParticipantAccountIDs.forEach((participantAccountID: number) => { - const participant = currentParticipants[participantAccountID]; - if (!targetAccountIDs.includes(participantAccountID)) { - participantAccountIDsAfterRemoval.push(participantAccountID); - if (!participant.hidden) { - visibleChatMemberAccountIDsAfterRemoval.push(participantAccountID); - } - } - }); - targetAccountIDs.forEach((accountID) => { removeParticipantsData[accountID] = null; }); @@ -2797,8 +2767,6 @@ function removeFromRoom(reportID: string, targetAccountIDs: number[]) { key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, value: { participants: removeParticipantsData, - participantAccountIDs: participantAccountIDsAfterRemoval, - visibleChatMemberAccountIDs: visibleChatMemberAccountIDsAfterRemoval, pendingChatMembers: report?.pendingChatMembers ?? null, }, }, diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index 9d1b3f6c0148..4e2331196a86 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -572,12 +572,7 @@ function editTaskAssignee( // If we make a change to the assignee, we want to add a comment to the assignee's chat // Check if the assignee actually changed if (assigneeAccountID && assigneeAccountID !== report.managerID && assigneeAccountID !== ownerAccountID && assigneeChatReport) { - const participants = report?.participantAccountIDs ?? []; - const visibleMembers = report.visibleChatMemberAccountIDs ?? []; - if (!visibleMembers.includes(assigneeAccountID)) { - optimisticReport.participantAccountIDs = [...participants, assigneeAccountID]; - optimisticReport.visibleChatMemberAccountIDs = [...visibleMembers, assigneeAccountID]; - } + optimisticReport.participants = {[assigneeAccountID]: {hidden: false}}; assigneeChatReportOnyxData = ReportUtils.getTaskAssigneeChatOnyxData( currentUserAccountID, diff --git a/src/pages/ProfilePage.tsx b/src/pages/ProfilePage.tsx index a8e4223c0180..448f4d1bcced 100755 --- a/src/pages/ProfilePage.tsx +++ b/src/pages/ProfilePage.tsx @@ -64,7 +64,7 @@ const getPhoneNumber = ({login = '', displayName = ''}: PersonalDetails | EmptyO const chatReportSelector = (report: OnyxEntry): OnyxEntry => report && { reportID: report.reportID, - participantAccountIDs: report.participantAccountIDs, + participants: report.participants, parentReportID: report.parentReportID, parentReportActionID: report.parentReportActionID, type: report.type, diff --git a/src/pages/home/HeaderView.tsx b/src/pages/home/HeaderView.tsx index 4295f1f0c46a..6b82f4ed154d 100644 --- a/src/pages/home/HeaderView.tsx +++ b/src/pages/home/HeaderView.tsx @@ -90,7 +90,11 @@ function HeaderView({ const isSelfDM = ReportUtils.isSelfDM(report); const isGroupChat = ReportUtils.isGroupChat(report) || ReportUtils.isDeprecatedGroupDM(report); // Currently, currentUser is not included in participantAccountIDs, so for selfDM, we need to add the currentUser as participants. - const participants = isSelfDM ? [session?.accountID ?? -1] : (report?.participantAccountIDs ?? []).slice(0, 5); + const participants = isSelfDM + ? [session?.accountID ?? -1] + : Object.keys(report.participants ?? {}) + .map(Number) + .slice(0, 5); const participantPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs(participants, personalDetails); const isMultipleParticipant = participants.length > 1; const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips(participantPersonalDetails, isMultipleParticipant, undefined, isSelfDM); diff --git a/src/pages/home/ReportScreen.tsx b/src/pages/home/ReportScreen.tsx index d84d610a171a..e5f46c1e23ba 100644 --- a/src/pages/home/ReportScreen.tsx +++ b/src/pages/home/ReportScreen.tsx @@ -197,7 +197,7 @@ function ReportScreen({ ownerAccountID: reportProp?.ownerAccountID, currency: reportProp?.currency, unheldTotal: reportProp?.unheldTotal, - participantAccountIDs: reportProp?.participantAccountIDs, + participants: reportProp?.participants, isWaitingOnBankAccount: reportProp?.isWaitingOnBankAccount, iouReportID: reportProp?.iouReportID, isOwnPolicyExpenseChat: reportProp?.isOwnPolicyExpenseChat, @@ -238,7 +238,7 @@ function ReportScreen({ reportProp?.ownerAccountID, reportProp?.currency, reportProp?.unheldTotal, - reportProp?.participantAccountIDs, + reportProp?.participants, reportProp?.isWaitingOnBankAccount, reportProp?.iouReportID, reportProp?.isOwnPolicyExpenseChat, diff --git a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx index 75d0c703b5b1..86d30ec136ff 100644 --- a/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx +++ b/src/pages/home/report/ReportActionCompose/ReportActionCompose.tsx @@ -176,10 +176,12 @@ function ReportActionCompose({ const suggestionsRef = useRef(null); const composerRef = useRef(null); - const reportParticipantIDs = useMemo( - () => report?.participantAccountIDs?.filter((accountID) => accountID !== currentUserPersonalDetails.accountID), - [currentUserPersonalDetails.accountID, report], + () => + Object.keys(report?.participants ?? {}) + .map(Number) + .filter((accountID) => accountID !== currentUserPersonalDetails.accountID), + [currentUserPersonalDetails.accountID, report?.participants], ); const shouldShowReportRecipientLocalTime = useMemo( @@ -187,7 +189,7 @@ function ReportActionCompose({ [personalDetails, report, currentUserPersonalDetails.accountID, isComposerFullSize], ); - const includesConcierge = useMemo(() => ReportUtils.chatIncludesConcierge({participantAccountIDs: report?.participantAccountIDs}), [report?.participantAccountIDs]); + const includesConcierge = useMemo(() => ReportUtils.chatIncludesConcierge({participants: report?.participants}), [report?.participants]); const userBlockedFromConcierge = useMemo(() => User.isBlockedFromConcierge(blockedFromConcierge), [blockedFromConcierge]); const isBlockedFromConcierge = useMemo(() => includesConcierge && userBlockedFromConcierge, [includesConcierge, userBlockedFromConcierge]); From 716c9446b893850119617abb157e2550c612e478 Mon Sep 17 00:00:00 2001 From: Abdelhafidh Belalia <16493223+s77rt@users.noreply.github.com> Date: Sun, 28 Apr 2024 20:52:17 +0100 Subject: [PATCH 06/21] Participants migration: IOUTest, PolicyTest, UnreadIndicatorsTest, OptionsListUtilsTest, ReportUtilsTest, LHNTestUtils --- tests/actions/IOUTest.ts | 41 ++++++++++++++++++++---------- tests/actions/PolicyTest.ts | 4 ++- tests/ui/UnreadIndicatorsTest.tsx | 4 +-- tests/unit/OptionsListUtilsTest.ts | 28 -------------------- tests/unit/ReportUtilsTest.ts | 8 +++--- tests/utils/LHNTestUtils.tsx | 3 ++- 6 files changed, 39 insertions(+), 49 deletions(-) diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index 45bdc5fe743a..ef2464d0e56c 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -19,6 +19,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; import type {IOUMessage, OriginalMessageIOU} from '@src/types/onyx/OriginalMessage'; +import {Participant} from '@src/types/onyx/Report'; import type {ReportActionBase} from '@src/types/onyx/ReportAction'; import {toCollectionDataSet} from '@src/types/utils/CollectionDataSet'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; @@ -36,12 +37,16 @@ jest.mock('@src/libs/Navigation/Navigation', () => ({ const CARLOS_EMAIL = 'cmartins@expensifail.com'; const CARLOS_ACCOUNT_ID = 1; +const CARLOS_PARTICIPANT: Participant = {hidden: false}; const JULES_EMAIL = 'jules@expensifail.com'; const JULES_ACCOUNT_ID = 2; +const JULES_PARTICIPANT: Participant = {hidden: false}; const RORY_EMAIL = 'rory@expensifail.com'; const RORY_ACCOUNT_ID = 3; +const RORY_PARTICIPANT: Participant = {hidden: false}; const VIT_EMAIL = 'vit@expensifail.com'; const VIT_ACCOUNT_ID = 4; +const VIT_PARTICIPANT: Participant = {hidden: false}; OnyxUpdateManager(); describe('actions/IOU', () => { @@ -96,7 +101,7 @@ describe('actions/IOU', () => { expect(iouReport?.notificationPreference).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN); // They should be linked together - expect(chatReport?.participantAccountIDs).toEqual([CARLOS_ACCOUNT_ID]); + expect(chatReport?.participants).toEqual({[CARLOS_ACCOUNT_ID]: CARLOS_PARTICIPANT}); expect(chatReport?.iouReportID).toBe(iouReport?.reportID); resolve(); @@ -249,7 +254,7 @@ describe('actions/IOU', () => { let chatReport: OnyxTypes.Report = { reportID: '1234', type: CONST.REPORT.TYPE.CHAT, - participantAccountIDs: [CARLOS_ACCOUNT_ID], + participants: {[CARLOS_ACCOUNT_ID]: CARLOS_PARTICIPANT}, }; const createdAction: OnyxTypes.ReportAction = { reportActionID: NumberUtils.rand64(), @@ -417,7 +422,7 @@ describe('actions/IOU', () => { reportID: chatReportID, type: CONST.REPORT.TYPE.CHAT, iouReportID, - participantAccountIDs: [CARLOS_ACCOUNT_ID], + participants: {[CARLOS_ACCOUNT_ID]: CARLOS_PARTICIPANT}, }; const createdAction: OnyxTypes.ReportAction = { reportActionID: NumberUtils.rand64(), @@ -641,7 +646,7 @@ describe('actions/IOU', () => { expect(iouReport?.notificationPreference).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN); // They should be linked together - expect(chatReport?.participantAccountIDs).toEqual([CARLOS_ACCOUNT_ID]); + expect(chatReport?.participants).toEqual({[CARLOS_ACCOUNT_ID]: CARLOS_PARTICIPANT}); expect(chatReport?.iouReportID).toBe(iouReport?.reportID); resolve(); @@ -937,7 +942,7 @@ describe('actions/IOU', () => { let carlosChatReport: OnyxEntry = { reportID: NumberUtils.rand64(), type: CONST.REPORT.TYPE.CHAT, - participantAccountIDs: [CARLOS_ACCOUNT_ID], + participants: {[CARLOS_ACCOUNT_ID]: CARLOS_PARTICIPANT}, }; const carlosCreatedAction: OnyxEntry = { reportActionID: NumberUtils.rand64(), @@ -950,7 +955,7 @@ describe('actions/IOU', () => { reportID: NumberUtils.rand64(), type: CONST.REPORT.TYPE.CHAT, iouReportID: julesIOUReportID, - participantAccountIDs: [JULES_ACCOUNT_ID], + participants: {[JULES_ACCOUNT_ID]: JULES_PARTICIPANT}, }; const julesChatCreatedAction: OnyxEntry = { reportActionID: NumberUtils.rand64(), @@ -1128,7 +1133,7 @@ describe('actions/IOU', () => { // 5. The chat report with Rory + Vit (new) vitChatReport = Object.values(allReports ?? {}).find( - (report) => report?.type === CONST.REPORT.TYPE.CHAT && isEqual(report.participantAccountIDs, [VIT_ACCOUNT_ID]), + (report) => report?.type === CONST.REPORT.TYPE.CHAT && isEqual(report.participants, {[VIT_ACCOUNT_ID]: VIT_PARTICIPANT}), ) ?? null; expect(isEmptyObject(vitChatReport)).toBe(false); expect(vitChatReport?.pendingFields).toStrictEqual({createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD}); @@ -1144,7 +1149,12 @@ describe('actions/IOU', () => { Object.values(allReports ?? {}).find( (report) => report?.type === CONST.REPORT.TYPE.CHAT && - isEqual(report.participantAccountIDs, [CARLOS_ACCOUNT_ID, JULES_ACCOUNT_ID, VIT_ACCOUNT_ID, RORY_ACCOUNT_ID]), + isEqual(report.participants, { + [CARLOS_ACCOUNT_ID]: CARLOS_PARTICIPANT, + [JULES_ACCOUNT_ID]: JULES_PARTICIPANT, + [VIT_ACCOUNT_ID]: VIT_PARTICIPANT, + [RORY_ACCOUNT_ID]: RORY_PARTICIPANT, + }), ) ?? null; expect(isEmptyObject(groupChat)).toBe(false); expect(groupChat?.pendingFields).toStrictEqual({createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD}); @@ -2454,7 +2464,8 @@ describe('actions/IOU', () => { jest.advanceTimersByTime(10); // Given User logins from the participant accounts - const userLogins = PersonalDetailsUtils.getLoginsByAccountIDs(thread?.participantAccountIDs ?? []); + const participantAccountIDs = Object.keys(thread.participants ?? {}).map(Number); + const userLogins = PersonalDetailsUtils.getLoginsByAccountIDs(participantAccountIDs); // When Opening a thread report with the given details Report.openReport(thread.reportID, '', userLogins, thread, createIOUAction?.reportActionID); @@ -2537,7 +2548,8 @@ describe('actions/IOU', () => { jest.advanceTimersByTime(10); // Given User logins from the participant accounts - const userLogins = PersonalDetailsUtils.getLoginsByAccountIDs(thread?.participantAccountIDs ?? []); + const participantAccountIDs = Object.keys(thread.participants ?? {}).map(Number); + const userLogins = PersonalDetailsUtils.getLoginsByAccountIDs(participantAccountIDs); // When Opening a thread report with the given details Report.openReport(thread.reportID, '', userLogins, thread, createIOUAction?.reportActionID); @@ -2627,7 +2639,8 @@ describe('actions/IOU', () => { expect(thread.notificationPreference).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN); - const userLogins = PersonalDetailsUtils.getLoginsByAccountIDs(thread?.participantAccountIDs ?? []); + const participantAccountIDs = Object.keys(thread.participants ?? {}).map(Number); + const userLogins = PersonalDetailsUtils.getLoginsByAccountIDs(participantAccountIDs); jest.advanceTimersByTime(10); Report.openReport(thread.reportID, '', userLogins, thread, createIOUAction?.reportActionID); await waitForBatchedUpdates(); @@ -2723,7 +2736,8 @@ describe('actions/IOU', () => { await waitForBatchedUpdates(); jest.advanceTimersByTime(10); - const userLogins = PersonalDetailsUtils.getLoginsByAccountIDs(thread?.participantAccountIDs ?? []); + const participantAccountIDs = Object.keys(thread.participants ?? {}).map(Number); + const userLogins = PersonalDetailsUtils.getLoginsByAccountIDs(participantAccountIDs); Report.openReport(thread.reportID, '', userLogins, thread, createIOUAction?.reportActionID); await waitForBatchedUpdates(); @@ -2958,7 +2972,8 @@ describe('actions/IOU', () => { await waitForBatchedUpdates(); jest.advanceTimersByTime(10); - const userLogins = PersonalDetailsUtils.getLoginsByAccountIDs(thread?.participantAccountIDs ?? []); + const participantAccountIDs = Object.keys(thread.participants ?? {}).map(Number); + const userLogins = PersonalDetailsUtils.getLoginsByAccountIDs(participantAccountIDs); Report.openReport(thread.reportID, '', userLogins, thread, createIOUAction?.reportActionID); await waitForBatchedUpdates(); diff --git a/tests/actions/PolicyTest.ts b/tests/actions/PolicyTest.ts index 44b69bcb86fe..6d8641e782d2 100644 --- a/tests/actions/PolicyTest.ts +++ b/tests/actions/PolicyTest.ts @@ -5,11 +5,13 @@ import OnyxUpdateManager from '@src/libs/actions/OnyxUpdateManager'; import * as Policy from '@src/libs/actions/Policy'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Policy as PolicyType, Report, ReportAction, ReportActions} from '@src/types/onyx'; +import {Participant} from '@src/types/onyx/Report'; import * as TestHelper from '../utils/TestHelper'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; const ESH_EMAIL = 'eshgupta1217@gmail.com'; const ESH_ACCOUNT_ID = 1; +const ESH_PARTICIPANT: Participant = {hidden: false, role: 'admin'}; const WORKSPACE_NAME = "Esh's Workspace"; OnyxUpdateManager(); @@ -77,7 +79,7 @@ describe('actions/Policy', () => { expect(workspaceReports.length).toBe(3); workspaceReports.forEach((report) => { expect(report?.pendingFields?.addWorkspaceRoom).toBe(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD); - expect(report?.participantAccountIDs).toEqual([ESH_ACCOUNT_ID]); + expect(report?.participants).toEqual({[ESH_ACCOUNT_ID]: ESH_PARTICIPANT}); switch (report?.chatType) { case CONST.REPORT.CHAT_TYPE.POLICY_ADMINS: { adminReportID = report.reportID; diff --git a/tests/ui/UnreadIndicatorsTest.tsx b/tests/ui/UnreadIndicatorsTest.tsx index 6c90a3dbf841..aeb4b3c46d1d 100644 --- a/tests/ui/UnreadIndicatorsTest.tsx +++ b/tests/ui/UnreadIndicatorsTest.tsx @@ -228,7 +228,7 @@ function signInAndGetAppWithUnreadChat(): Promise { lastReadTime: reportAction3CreatedDate, lastVisibleActionCreated: reportAction9CreatedDate, lastMessageText: 'Test', - participantAccountIDs: [USER_B_ACCOUNT_ID], + participants: {[USER_B_ACCOUNT_ID]: {hidden: false}}, lastActorAccountID: USER_B_ACCOUNT_ID, type: CONST.REPORT.TYPE.CHAT, }); @@ -389,7 +389,7 @@ describe('Unread Indicators', () => { lastVisibleActionCreated: DateUtils.getDBTime(utcToZonedTime(NEW_REPORT_FIST_MESSAGE_CREATED_DATE, 'UTC').valueOf()), lastMessageText: 'Comment 1', lastActorAccountID: USER_C_ACCOUNT_ID, - participantAccountIDs: [USER_C_ACCOUNT_ID], + participants: {[USER_C_ACCOUNT_ID]: {hidden: false}}, type: CONST.REPORT.TYPE.CHAT, }, }, diff --git a/tests/unit/OptionsListUtilsTest.ts b/tests/unit/OptionsListUtilsTest.ts index 76b4324f697b..21cd2f1fe58a 100644 --- a/tests/unit/OptionsListUtilsTest.ts +++ b/tests/unit/OptionsListUtilsTest.ts @@ -19,8 +19,6 @@ describe('OptionsListUtils', () => { lastVisibleActionCreated: '2022-11-22 03:26:02.015', isPinned: false, reportID: '1', - participantAccountIDs: [2, 1], - visibleChatMemberAccountIDs: [2, 1], participants: { 2: {}, 1: {}, @@ -33,8 +31,6 @@ describe('OptionsListUtils', () => { lastVisibleActionCreated: '2022-11-22 03:26:02.016', isPinned: false, reportID: '2', - participantAccountIDs: [3], - visibleChatMemberAccountIDs: [3], participants: { 3: {}, }, @@ -48,8 +44,6 @@ describe('OptionsListUtils', () => { lastVisibleActionCreated: '2022-11-22 03:26:02.170', isPinned: true, reportID: '3', - participantAccountIDs: [1], - visibleChatMemberAccountIDs: [1], participants: { 1: {}, }, @@ -61,8 +55,6 @@ describe('OptionsListUtils', () => { lastVisibleActionCreated: '2022-11-22 03:26:02.180', isPinned: false, reportID: '4', - participantAccountIDs: [4], - visibleChatMemberAccountIDs: [4], participants: { 4: {}, }, @@ -74,8 +66,6 @@ describe('OptionsListUtils', () => { lastVisibleActionCreated: '2022-11-22 03:26:02.019', isPinned: false, reportID: '5', - participantAccountIDs: [5], - visibleChatMemberAccountIDs: [5], participants: { 5: {}, }, @@ -87,8 +77,6 @@ describe('OptionsListUtils', () => { lastVisibleActionCreated: '2022-11-22 03:26:02.020', isPinned: false, reportID: '6', - participantAccountIDs: [6], - visibleChatMemberAccountIDs: [6], participants: { 6: {}, }, @@ -102,8 +90,6 @@ describe('OptionsListUtils', () => { lastVisibleActionCreated: '2022-11-22 03:26:03.999', isPinned: false, reportID: '7', - participantAccountIDs: [7], - visibleChatMemberAccountIDs: [7], participants: { 7: {}, }, @@ -117,8 +103,6 @@ describe('OptionsListUtils', () => { lastVisibleActionCreated: '2022-11-22 03:26:02.000', isPinned: false, reportID: '8', - participantAccountIDs: [12], - visibleChatMemberAccountIDs: [12], participants: { 12: {}, }, @@ -132,8 +116,6 @@ describe('OptionsListUtils', () => { lastVisibleActionCreated: '2022-11-22 03:26:02.998', isPinned: false, reportID: '9', - participantAccountIDs: [8], - visibleChatMemberAccountIDs: [8], participants: { 8: {}, }, @@ -148,8 +130,6 @@ describe('OptionsListUtils', () => { lastVisibleActionCreated: '2022-11-22 03:26:02.001', reportID: '10', isPinned: false, - participantAccountIDs: [2, 7], - visibleChatMemberAccountIDs: [2, 7], participants: { 2: {}, 7: {}, @@ -242,8 +222,6 @@ describe('OptionsListUtils', () => { lastVisibleActionCreated: '2022-11-22 03:26:02.022', isPinned: false, reportID: '11', - participantAccountIDs: [999], - visibleChatMemberAccountIDs: [999], participants: { 999: {}, }, @@ -259,8 +237,6 @@ describe('OptionsListUtils', () => { lastVisibleActionCreated: '2022-11-22 03:26:02.022', isPinned: false, reportID: '12', - participantAccountIDs: [1000], - visibleChatMemberAccountIDs: [1000], participants: { 1000: {}, }, @@ -276,8 +252,6 @@ describe('OptionsListUtils', () => { lastVisibleActionCreated: '2022-11-22 03:26:02.022', isPinned: false, reportID: '13', - participantAccountIDs: [1001], - visibleChatMemberAccountIDs: [1001], participants: { 1001: {}, }, @@ -293,8 +267,6 @@ describe('OptionsListUtils', () => { lastVisibleActionCreated: '2022-11-22 03:26:02.022', isPinned: false, reportID: '14', - participantAccountIDs: [1, 10, 3], - visibleChatMemberAccountIDs: [1, 10, 3], participants: { 1: {}, 10: {}, diff --git a/tests/unit/ReportUtilsTest.ts b/tests/unit/ReportUtilsTest.ts index d0ea948bdb6c..3cf938ed1d4c 100644 --- a/tests/unit/ReportUtilsTest.ts +++ b/tests/unit/ReportUtilsTest.ts @@ -118,7 +118,7 @@ describe('ReportUtils', () => { expect( ReportUtils.getReportName({ reportID: '', - participantAccountIDs: [currentUserAccountID, 1], + participants: ReportUtils.buildParticipantsFromAccountIDs([currentUserAccountID, 1]), }), ).toBe('Ragnar Lothbrok'); }); @@ -127,7 +127,7 @@ describe('ReportUtils', () => { expect( ReportUtils.getReportName({ reportID: '', - participantAccountIDs: [currentUserAccountID, 2], + participants: ReportUtils.buildParticipantsFromAccountIDs([currentUserAccountID, 2]), }), ).toBe('floki@vikings.net'); }); @@ -136,7 +136,7 @@ describe('ReportUtils', () => { expect( ReportUtils.getReportName({ reportID: '', - participantAccountIDs: [currentUserAccountID, 4], + participants: ReportUtils.buildParticipantsFromAccountIDs([currentUserAccountID, 4]), }), ).toBe('(833) 240-3627'); }); @@ -146,7 +146,7 @@ describe('ReportUtils', () => { expect( ReportUtils.getReportName({ reportID: '', - participantAccountIDs: [currentUserAccountID, 1, 2, 3, 4], + participants: ReportUtils.buildParticipantsFromAccountIDs([currentUserAccountID, 1, 2, 3, 4]), }), ).toBe('Ragnar, floki@vikings.net, Lagertha, (833) 240-3627'); }); diff --git a/tests/utils/LHNTestUtils.tsx b/tests/utils/LHNTestUtils.tsx index e3daa93a3179..34a7b2e0a842 100644 --- a/tests/utils/LHNTestUtils.tsx +++ b/tests/utils/LHNTestUtils.tsx @@ -10,6 +10,7 @@ import {CurrentReportIDContextProvider} from '@components/withCurrentReportID'; import {EnvironmentProvider} from '@components/withEnvironment'; import {ReportIDsContextProvider} from '@hooks/useReportIDs'; import DateUtils from '@libs/DateUtils'; +import * as ReportUtils from '@libs/ReportUtils'; import ReportActionItemSingle from '@pages/home/report/ReportActionItemSingle'; import SidebarLinksData from '@pages/home/sidebar/SidebarLinksData'; import CONST from '@src/CONST'; @@ -128,7 +129,7 @@ function getFakeReport(participantAccountIDs = [1, 2], millisecondsInThePast = 0 reportName: 'Report', lastVisibleActionCreated, lastReadTime: isUnread ? DateUtils.subtractMillisecondsFromDateTime(lastVisibleActionCreated, 1) : lastVisibleActionCreated, - participantAccountIDs, + participants: ReportUtils.buildParticipantsFromAccountIDs(participantAccountIDs), }; } From 4ec3fb5cd5d85a70886d82d9ea626b088d373b97 Mon Sep 17 00:00:00 2001 From: Abdelhafidh Belalia <16493223+s77rt@users.noreply.github.com> Date: Sun, 28 Apr 2024 21:12:33 +0100 Subject: [PATCH 07/21] Participants migration: useReportIDs, RoomInvitePage, RoomMembersPage, OptionsListUtilsTest --- src/hooks/useReportIDs.tsx | 2 +- src/pages/RoomInvitePage.tsx | 15 ++--- src/pages/RoomMembersPage.tsx | 94 ++++++++++++++++-------------- tests/unit/OptionsListUtilsTest.ts | 4 +- 4 files changed, 59 insertions(+), 56 deletions(-) diff --git a/src/hooks/useReportIDs.tsx b/src/hooks/useReportIDs.tsx index 58d4e42cd83b..54026ab81ffb 100644 --- a/src/hooks/useReportIDs.tsx +++ b/src/hooks/useReportIDs.tsx @@ -38,7 +38,7 @@ const ReportIDsContext = createContext({ const chatReportSelector = (report: OnyxEntry): ChatReportSelector => (report && { reportID: report.reportID, - participantAccountIDs: report.participantAccountIDs, + participants: report.participants, isPinned: report.isPinned, isHidden: report.isHidden, notificationPreference: report.notificationPreference, diff --git a/src/pages/RoomInvitePage.tsx b/src/pages/RoomInvitePage.tsx index 703a87c9510a..77ba81b1e18f 100644 --- a/src/pages/RoomInvitePage.tsx +++ b/src/pages/RoomInvitePage.tsx @@ -59,13 +59,14 @@ function RoomInvitePage({ }, []); // Any existing participants and Expensify emails should not be eligible for invitation - const excludedUsers = useMemo( - () => - [...PersonalDetailsUtils.getLoginsByAccountIDs(report?.visibleChatMemberAccountIDs ?? []), ...CONST.EXPENSIFY_EMAILS].map((participant) => - PhoneNumber.addSMSDomainIfPhoneNumber(participant), - ), - [report], - ); + const excludedUsers = useMemo(() => { + const visibleParticipantAccountIDs = Object.entries(report.participants ?? {}) + .filter(([_, participant]) => participant && !participant.hidden) + .map(([accountID]) => Number(accountID)); + return [...PersonalDetailsUtils.getLoginsByAccountIDs(visibleParticipantAccountIDs), ...CONST.EXPENSIFY_EMAILS].map((participant) => + PhoneNumber.addSMSDomainIfPhoneNumber(participant), + ); + }, [report.participants]); useEffect(() => { const inviteOptions = OptionsListUtils.getMemberInviteOptions(options.personalDetails, betas ?? [], searchTerm, excludedUsers); diff --git a/src/pages/RoomMembersPage.tsx b/src/pages/RoomMembersPage.tsx index 67b6d96f182d..1c3f8dfc803e 100644 --- a/src/pages/RoomMembersPage.tsx +++ b/src/pages/RoomMembersPage.tsx @@ -167,58 +167,62 @@ function RoomMembersPage({report, session, policies}: RoomMembersPageProps) { const getMemberOptions = (): ListItem[] => { let result: ListItem[] = []; - report?.visibleChatMemberAccountIDs?.forEach((accountID) => { - const details = personalDetails[accountID]; + Object.entries(report.participants ?? {}) + .filter(([_, participant]) => participant && !participant.hidden) + .forEach(([accountIDKey]) => { + const accountID = Number(accountIDKey); - if (!details) { - Log.hmmm(`[RoomMembersPage] no personal details found for room member with accountID: ${accountID}`); - return; - } + const details = personalDetails[accountID]; - // If search value is provided, filter out members that don't match the search value - if (searchValue.trim()) { - let memberDetails = ''; - if (details.login) { - memberDetails += ` ${details.login.toLowerCase()}`; - } - if (details.firstName) { - memberDetails += ` ${details.firstName.toLowerCase()}`; - } - if (details.lastName) { - memberDetails += ` ${details.lastName.toLowerCase()}`; - } - if (details.displayName) { - memberDetails += ` ${PersonalDetailsUtils.getDisplayNameOrDefault(details).toLowerCase()}`; - } - if (details.phoneNumber) { - memberDetails += ` ${details.phoneNumber.toLowerCase()}`; + if (!details) { + Log.hmmm(`[RoomMembersPage] no personal details found for room member with accountID: ${accountID}`); + return; } - if (!OptionsListUtils.isSearchStringMatch(searchValue.trim(), memberDetails)) { - return; + // If search value is provided, filter out members that don't match the search value + if (searchValue.trim()) { + let memberDetails = ''; + if (details.login) { + memberDetails += ` ${details.login.toLowerCase()}`; + } + if (details.firstName) { + memberDetails += ` ${details.firstName.toLowerCase()}`; + } + if (details.lastName) { + memberDetails += ` ${details.lastName.toLowerCase()}`; + } + if (details.displayName) { + memberDetails += ` ${PersonalDetailsUtils.getDisplayNameOrDefault(details).toLowerCase()}`; + } + if (details.phoneNumber) { + memberDetails += ` ${details.phoneNumber.toLowerCase()}`; + } + + if (!OptionsListUtils.isSearchStringMatch(searchValue.trim(), memberDetails)) { + return; + } } - } - const pendingChatMember = report?.pendingChatMembers?.findLast((member) => member.accountID === accountID.toString()); + const pendingChatMember = report?.pendingChatMembers?.findLast((member) => member.accountID === accountID.toString()); - result.push({ - keyForList: String(accountID), - accountID, - isSelected: selectedMembers.includes(accountID), - isDisabled: accountID === session?.accountID || pendingChatMember?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, - text: formatPhoneNumber(PersonalDetailsUtils.getDisplayNameOrDefault(details)), - alternateText: details?.login ? formatPhoneNumber(details.login) : '', - icons: [ - { - source: UserUtils.getAvatar(details.avatar, accountID), - name: details.login ?? '', - type: CONST.ICON_TYPE_AVATAR, - id: Number(accountID), - }, - ], - pendingAction: pendingChatMember?.pendingAction, - errors: pendingChatMember?.errors, + result.push({ + keyForList: String(accountID), + accountID, + isSelected: selectedMembers.includes(accountID), + isDisabled: accountID === session?.accountID || pendingChatMember?.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, + text: formatPhoneNumber(PersonalDetailsUtils.getDisplayNameOrDefault(details)), + alternateText: details?.login ? formatPhoneNumber(details.login) : '', + icons: [ + { + source: UserUtils.getAvatar(details.avatar, accountID), + name: details.login ?? '', + type: CONST.ICON_TYPE_AVATAR, + id: Number(accountID), + }, + ], + pendingAction: pendingChatMember?.pendingAction, + errors: pendingChatMember?.errors, + }); }); - }); result = result.sort((value1, value2) => localeCompare(value1.text ?? '', value2.text ?? '')); diff --git a/tests/unit/OptionsListUtilsTest.ts b/tests/unit/OptionsListUtilsTest.ts index 21cd2f1fe58a..a59894c8c7f4 100644 --- a/tests/unit/OptionsListUtilsTest.ts +++ b/tests/unit/OptionsListUtilsTest.ts @@ -280,15 +280,13 @@ describe('OptionsListUtils', () => { }, }; - const REPORTS_WITH_CHAT_ROOM = { + const REPORTS_WITH_CHAT_ROOM: OnyxCollection = { ...REPORTS, 15: { lastReadTime: '2021-01-14 11:25:39.301', lastVisibleActionCreated: '2022-11-22 03:26:02.000', isPinned: false, reportID: '15', - participantAccountIDs: [3, 4], - visibleChatMemberAccountIDs: [3, 4], participants: { 3: {}, 4: {}, From a3dcdda85a3f877b84a5e5b5fffd5efa5ed0248e Mon Sep 17 00:00:00 2001 From: Abdelhafidh Belalia <16493223+s77rt@users.noreply.github.com> Date: Sun, 28 Apr 2024 21:33:01 +0100 Subject: [PATCH 08/21] Participants migration: lint --- src/libs/OptionsListUtils.ts | 6 +++--- src/libs/SidebarUtils.ts | 2 +- src/pages/RoomInvitePage.tsx | 2 +- src/pages/RoomMembersPage.tsx | 2 +- tests/actions/IOUTest.ts | 2 +- tests/actions/PolicyTest.ts | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 322b690820e8..74ef7bef2453 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -696,7 +696,7 @@ function createOption( result.isSelfDM = ReportUtils.isSelfDM(report); const visibleParticipantAccountIDs = Object.entries(report.participants ?? {}) - .filter(([_, participant]) => participant && !participant.hidden) + .filter(([, participant]) => participant && !participant.hidden) .map(([accountID]) => Number(accountID)); result.tooltipText = ReportUtils.getReportParticipantsTitle(visibleParticipantAccountIDs); @@ -765,7 +765,7 @@ function getReportOption(participant: Participant): ReportUtils.OptionData { const report = ReportUtils.getReport(participant.reportID); const visibleParticipantAccountIDs = Object.entries(report?.participants ?? {}) - .filter(([_, participant]) => participant && !participant.hidden) + .filter(([, reportParticipant]) => reportParticipant && !reportParticipant.hidden) .map(([accountID]) => Number(accountID)); const option = createOption( @@ -798,7 +798,7 @@ function getPolicyExpenseReportOption(participant: Participant | ReportUtils.Opt const expenseReport = ReportUtils.isPolicyExpenseChat(participant) ? ReportUtils.getReport(participant.reportID) : null; const visibleParticipantAccountIDs = Object.entries(expenseReport?.participants ?? {}) - .filter(([_, participant]) => participant && !participant.hidden) + .filter(([, reportParticipant]) => reportParticipant && !reportParticipant.hidden) .map(([accountID]) => Number(accountID)); const option = createOption( diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 9d091543eeb0..e97f0dfc35fb 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -281,7 +281,7 @@ function getOptionData({ result.isSelfDM = ReportUtils.isSelfDM(report); const visibleParticipantAccountIDs = Object.entries(report.participants ?? {}) - .filter(([_, participant]) => participant && !participant.hidden) + .filter(([, participant]) => participant && !participant.hidden) .map(([accountID]) => Number(accountID)); result.tooltipText = ReportUtils.getReportParticipantsTitle(visibleParticipantAccountIDs); diff --git a/src/pages/RoomInvitePage.tsx b/src/pages/RoomInvitePage.tsx index 77ba81b1e18f..84e27fbc9a89 100644 --- a/src/pages/RoomInvitePage.tsx +++ b/src/pages/RoomInvitePage.tsx @@ -61,7 +61,7 @@ function RoomInvitePage({ // Any existing participants and Expensify emails should not be eligible for invitation const excludedUsers = useMemo(() => { const visibleParticipantAccountIDs = Object.entries(report.participants ?? {}) - .filter(([_, participant]) => participant && !participant.hidden) + .filter(([, participant]) => participant && !participant.hidden) .map(([accountID]) => Number(accountID)); return [...PersonalDetailsUtils.getLoginsByAccountIDs(visibleParticipantAccountIDs), ...CONST.EXPENSIFY_EMAILS].map((participant) => PhoneNumber.addSMSDomainIfPhoneNumber(participant), diff --git a/src/pages/RoomMembersPage.tsx b/src/pages/RoomMembersPage.tsx index 1c3f8dfc803e..a22860cb8c48 100644 --- a/src/pages/RoomMembersPage.tsx +++ b/src/pages/RoomMembersPage.tsx @@ -168,7 +168,7 @@ function RoomMembersPage({report, session, policies}: RoomMembersPageProps) { let result: ListItem[] = []; Object.entries(report.participants ?? {}) - .filter(([_, participant]) => participant && !participant.hidden) + .filter(([, participant]) => participant && !participant.hidden) .forEach(([accountIDKey]) => { const accountID = Number(accountIDKey); diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index ef2464d0e56c..82ae6dd414e3 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -19,7 +19,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type * as OnyxTypes from '@src/types/onyx'; import type {IOUMessage, OriginalMessageIOU} from '@src/types/onyx/OriginalMessage'; -import {Participant} from '@src/types/onyx/Report'; +import type {Participant} from '@src/types/onyx/Report'; import type {ReportActionBase} from '@src/types/onyx/ReportAction'; import {toCollectionDataSet} from '@src/types/utils/CollectionDataSet'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; diff --git a/tests/actions/PolicyTest.ts b/tests/actions/PolicyTest.ts index 6d8641e782d2..3e11f0a67938 100644 --- a/tests/actions/PolicyTest.ts +++ b/tests/actions/PolicyTest.ts @@ -5,7 +5,7 @@ import OnyxUpdateManager from '@src/libs/actions/OnyxUpdateManager'; import * as Policy from '@src/libs/actions/Policy'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Policy as PolicyType, Report, ReportAction, ReportActions} from '@src/types/onyx'; -import {Participant} from '@src/types/onyx/Report'; +import type {Participant} from '@src/types/onyx/Report'; import * as TestHelper from '../utils/TestHelper'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; From ccb420bae7d608b92eb084efb6cf9b318ee0cc31 Mon Sep 17 00:00:00 2001 From: Abdelhafidh Belalia <16493223+s77rt@users.noreply.github.com> Date: Thu, 2 May 2024 17:46:01 +0100 Subject: [PATCH 09/21] Fix isMultipleParticipant condition --- src/components/OptionRow.tsx | 4 ++-- src/components/ReportWelcomeText.tsx | 16 +++++++++++----- src/libs/ReportUtils.ts | 24 +++++++++++++++--------- src/libs/actions/Task.ts | 16 +++++++++++----- src/pages/home/HeaderView.tsx | 24 ++++++++++++++---------- 5 files changed, 53 insertions(+), 31 deletions(-) diff --git a/src/components/OptionRow.tsx b/src/components/OptionRow.tsx index 90ccff47b2b9..f8a6f3f73a71 100644 --- a/src/components/OptionRow.tsx +++ b/src/components/OptionRow.tsx @@ -144,10 +144,10 @@ function OptionRow({ const hoveredStyle = hoverStyle ? flattenHoverStyle : styles.sidebarLinkHover; const hoveredBackgroundColor = hoveredStyle?.backgroundColor ? (hoveredStyle.backgroundColor as string) : backgroundColor; const focusedBackgroundColor = styles.sidebarLinkActive.backgroundColor; - const isMultipleParticipant = (option.participantsList?.length ?? 0) > 1; + const shouldUseShortFormInTooltip = (option.participantsList?.length ?? 0) > 1; // We only create tooltips for the first 10 users or so since some reports have hundreds of users, causing performance to degrade. - const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips((option.participantsList ?? (option.accountID ? [option] : [])).slice(0, 10), isMultipleParticipant); + const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips((option.participantsList ?? (option.accountID ? [option] : [])).slice(0, 10), shouldUseShortFormInTooltip); let subscriptColor = theme.appBG; if (optionIsFocused) { subscriptColor = focusedBackgroundColor; diff --git a/src/components/ReportWelcomeText.tsx b/src/components/ReportWelcomeText.tsx index aa67657f6fc2..c589f19d2a90 100644 --- a/src/components/ReportWelcomeText.tsx +++ b/src/components/ReportWelcomeText.tsx @@ -1,7 +1,7 @@ import React, {useMemo} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; -import {withOnyx} from 'react-native-onyx'; +import {useOnyx, withOnyx} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; import usePermissions from '@hooks/usePermissions'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -35,18 +35,24 @@ type ReportWelcomeTextProps = ReportWelcomeTextOnyxProps & { function ReportWelcomeText({report, policy, personalDetails}: ReportWelcomeTextProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); + const [session] = useOnyx(ONYXKEYS.SESSION); const {canUseTrackExpense} = usePermissions(); const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(report); const isChatRoom = ReportUtils.isChatRoom(report); const isSelfDM = ReportUtils.isSelfDM(report); const isInvoiceRoom = ReportUtils.isInvoiceRoom(report); const isDefault = !(isChatRoom || isPolicyExpenseChat || isSelfDM || isInvoiceRoom); - const participantAccountIDs = Object.keys(report?.participants ?? {}).map(Number); - const isMultipleParticipant = participantAccountIDs.length > 1; - const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips(OptionsListUtils.getPersonalDetailsForAccountIDs(participantAccountIDs, personalDetails), isMultipleParticipant); + const otherParticipantsAccountIDs = Object.keys(report?.participants ?? {}) + .map(Number) + .filter((accountID) => accountID !== session?.accountID); + const hasMultipleOtherParticipants = otherParticipantsAccountIDs.length > 1; + const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips( + OptionsListUtils.getPersonalDetailsForAccountIDs(otherParticipantsAccountIDs, personalDetails), + hasMultipleOtherParticipants, + ); const isUserPolicyAdmin = PolicyUtils.isPolicyAdmin(policy); const roomWelcomeMessage = ReportUtils.getRoomWelcomeMessage(report, isUserPolicyAdmin); - const moneyRequestOptions = ReportUtils.temporary_getMoneyRequestOptions(report, policy, participantAccountIDs, canUseTrackExpense); + const moneyRequestOptions = ReportUtils.temporary_getMoneyRequestOptions(report, policy, otherParticipantsAccountIDs, canUseTrackExpense); const additionalText = moneyRequestOptions .filter((item): item is Exclude => item !== CONST.IOU.TYPE.INVOICE) .map((item) => translate(`reportActionsView.iouTypes.${item}`)) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 9d97a3deb5a3..2e2c7f9c4d3f 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1280,8 +1280,12 @@ function isPolicyAdmin(policyID: string, policies: OnyxCollection): bool /** * Returns true if report has a single participant. */ -function hasSingleParticipant(report: OnyxEntry): boolean { - return Object.keys(report?.participants ?? {}).length === 1; +function hasSingleOtherParticipant(report: OnyxEntry): boolean { + return ( + Object.keys(report?.participants ?? {}) + .map(Number) + .filter((accountID) => accountID !== currentUserAccountID).length === 1 + ); } /** @@ -1578,7 +1582,7 @@ function getReportRecipientAccountIDs(report: OnyxEntry, currentLoginAcc // get parent report and use its participants array. if (isThread(report) && !(isTaskReport(report) || isMoneyRequestReport(report))) { const parentReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID}`] ?? null; - if (hasSingleParticipant(parentReport)) { + if (hasSingleOtherParticipant(parentReport)) { finalReport = parentReport; } } @@ -2050,7 +2054,7 @@ function getIcons( function getDisplayNamesWithTooltips( personalDetailsList: PersonalDetails[] | PersonalDetailsList | OptionData[], - isMultipleParticipantReport: boolean, + shouldUseShortForm: boolean, shouldFallbackToHidden = true, shouldAddCurrentUserPostfix = false, ): DisplayNameWithTooltips { @@ -2060,7 +2064,7 @@ function getDisplayNamesWithTooltips( .map((user) => { const accountID = Number(user?.accountID); // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - const displayName = getDisplayNameForParticipant(accountID, isMultipleParticipantReport, shouldFallbackToHidden, shouldAddCurrentUserPostfix) || user?.login || ''; + const displayName = getDisplayNameForParticipant(accountID, shouldUseShortForm, shouldFallbackToHidden, shouldAddCurrentUserPostfix) || user?.login || ''; const avatar = UserUtils.getDefaultAvatar(accountID); let pronouns = user?.pronouns ?? undefined; @@ -3179,8 +3183,8 @@ function getReportName(report: OnyxEntry, policy: OnyxEntry = nu .map(Number) .filter((accountID) => accountID !== currentUserAccountID) .slice(0, 5); - const isMultipleParticipantReport = participantsWithoutCurrentUser.length > 1; - return participantsWithoutCurrentUser.map((accountID) => getDisplayNameForParticipant(accountID, isMultipleParticipantReport)).join(', '); + const hasMultipleOtherParticipants = participantsWithoutCurrentUser.length > 1; + return participantsWithoutCurrentUser.map((accountID) => getDisplayNameForParticipant(accountID, hasMultipleOtherParticipants)).join(', '); } /** @@ -5955,7 +5959,9 @@ function isDeprecatedGroupDM(report: OnyxEntry): boolean { !isMoneyRequestReport(report) && !isArchivedRoom(report) && !Object.values(CONST.REPORT.CHAT_TYPE).some((chatType) => chatType === getChatType(report)) && - Object.keys(report.participants ?? {}).length > 1, + Object.keys(report.participants ?? {}) + .map(Number) + .filter((accountID) => accountID !== currentUserAccountID).length > 1, ); } @@ -6628,7 +6634,7 @@ export { hasOnlyHeldExpenses, hasOnlyTransactionsWithPendingRoutes, hasReportNameError, - hasSingleParticipant, + hasSingleOtherParticipant, hasSmartscanError, hasUpdatedTotal, hasViolations, diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index 4e2331196a86..e893b9e320bd 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -762,13 +762,19 @@ function getAssignee(assigneeAccountID: number, personalDetails: OnyxEntry, personalDetails: OnyxEntry): ShareDestination { const report = reports?.[`report_${reportID}`] ?? null; - const participantAccountIDs = Object.keys(report?.participants ?? {}).map(Number); - const isMultipleParticipant = participantAccountIDs.length > 1; - const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips(OptionsListUtils.getPersonalDetailsForAccountIDs(participantAccountIDs, personalDetails), isMultipleParticipant); + const otherParticipantsAccountIDs = Object.keys(report?.participants ?? {}) + .map(Number) + .filter((accountID) => accountID !== currentUserAccountID); + + const hasMultipleOtherParticipants = otherParticipantsAccountIDs.length > 1; + const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips( + OptionsListUtils.getPersonalDetailsForAccountIDs(otherParticipantsAccountIDs, personalDetails), + hasMultipleOtherParticipants, + ); let subtitle = ''; - if (ReportUtils.isChatReport(report) && ReportUtils.isDM(report) && ReportUtils.hasSingleParticipant(report)) { - const participantAccountID = participantAccountIDs[0] ?? -1; + if (ReportUtils.isChatReport(report) && ReportUtils.isDM(report) && ReportUtils.hasSingleOtherParticipant(report)) { + const participantAccountID = otherParticipantsAccountIDs[0] ?? -1; const displayName = personalDetails?.[participantAccountID]?.displayName ?? ''; const login = personalDetails?.[participantAccountID]?.login ?? ''; diff --git a/src/pages/home/HeaderView.tsx b/src/pages/home/HeaderView.tsx index e31481886055..f6d3d20d75ad 100644 --- a/src/pages/home/HeaderView.tsx +++ b/src/pages/home/HeaderView.tsx @@ -89,15 +89,19 @@ function HeaderView({ const styles = useThemeStyles(); const isSelfDM = ReportUtils.isSelfDM(report); const isGroupChat = ReportUtils.isGroupChat(report) || ReportUtils.isDeprecatedGroupDM(report); - // Currently, currentUser is not included in participantAccountIDs, so for selfDM, we need to add the currentUser as participants. - const participants = isSelfDM - ? [session?.accountID ?? -1] - : Object.keys(report.participants ?? {}) - .map(Number) - .slice(0, 5); - const participantPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs(participants, personalDetails); - const isMultipleParticipant = participants.length > 1; - const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips(participantPersonalDetails, isMultipleParticipant, undefined, isSelfDM); + + const allParticipantsAccountIDs = useMemo(() => Object.keys(report.participants ?? {}).map(Number), [report.participants]); + const isMultipleParticipant = allParticipantsAccountIDs.length > 1; + + const shouldIncludeCurrentUser = isSelfDM || isGroupChat; + const participantsAccountIDs = useMemo( + () => allParticipantsAccountIDs.filter((accountID) => shouldIncludeCurrentUser || accountID !== session?.accountID).slice(0, 5), + [shouldIncludeCurrentUser, allParticipantsAccountIDs, session?.accountID], + ); + + const shouldUseShortFormInTooltip = participantsAccountIDs.length > 1; + const participantPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs(participantsAccountIDs, personalDetails); + const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips(participantPersonalDetails, shouldUseShortFormInTooltip, undefined, isSelfDM); const isChatThread = ReportUtils.isChatThread(report); const isChatRoom = ReportUtils.isChatRoom(report); @@ -108,7 +112,7 @@ function HeaderView({ const title = ReportUtils.getReportName(reportHeaderData); const subtitle = ReportUtils.getChatRoomSubtitle(reportHeaderData); const parentNavigationSubtitleData = ReportUtils.getParentNavigationSubtitle(reportHeaderData); - const isConcierge = ReportUtils.hasSingleParticipant(report) && participants.includes(CONST.ACCOUNT_ID.CONCIERGE); + const isConcierge = ReportUtils.hasSingleOtherParticipant(report) && participantsAccountIDs.includes(CONST.ACCOUNT_ID.CONCIERGE); const isCanceledTaskReport = ReportUtils.isCanceledTaskReport(report, parentReportAction); const isWhisperAction = ReportActionsUtils.isWhisperAction(parentReportAction); const isUserCreatedPolicyRoom = ReportUtils.isUserCreatedPolicyRoom(report); From 9f6afd8a966e2cb09d62685e86243b68b091e95e Mon Sep 17 00:00:00 2001 From: Abdelhafidh Belalia <16493223+s77rt@users.noreply.github.com> Date: Thu, 2 May 2024 19:57:38 +0100 Subject: [PATCH 10/21] fix participants length --- src/components/ReportWelcomeText.tsx | 14 +++--- src/libs/OptionsListUtils.ts | 45 +++++++---------- src/libs/ReportUtils.ts | 73 +++++++++++++++------------- src/libs/SidebarUtils.ts | 19 +++++--- src/libs/actions/Task.ts | 17 +++---- src/pages/home/HeaderView.tsx | 21 ++++---- 6 files changed, 93 insertions(+), 96 deletions(-) diff --git a/src/components/ReportWelcomeText.tsx b/src/components/ReportWelcomeText.tsx index c589f19d2a90..4fc44d3af58e 100644 --- a/src/components/ReportWelcomeText.tsx +++ b/src/components/ReportWelcomeText.tsx @@ -41,18 +41,16 @@ function ReportWelcomeText({report, policy, personalDetails}: ReportWelcomeTextP const isChatRoom = ReportUtils.isChatRoom(report); const isSelfDM = ReportUtils.isSelfDM(report); const isInvoiceRoom = ReportUtils.isInvoiceRoom(report); + const isOneOnOneChat = ReportUtils.isOneOnOneChat(report); const isDefault = !(isChatRoom || isPolicyExpenseChat || isSelfDM || isInvoiceRoom); - const otherParticipantsAccountIDs = Object.keys(report?.participants ?? {}) + const participantAccountIDs = Object.keys(report?.participants ?? {}) .map(Number) - .filter((accountID) => accountID !== session?.accountID); - const hasMultipleOtherParticipants = otherParticipantsAccountIDs.length > 1; - const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips( - OptionsListUtils.getPersonalDetailsForAccountIDs(otherParticipantsAccountIDs, personalDetails), - hasMultipleOtherParticipants, - ); + .filter((accountID) => !isOneOnOneChat || accountID !== session?.accountID); + const isMultipleParticipant = participantAccountIDs.length > 1; + const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips(OptionsListUtils.getPersonalDetailsForAccountIDs(participantAccountIDs, personalDetails), isMultipleParticipant); const isUserPolicyAdmin = PolicyUtils.isPolicyAdmin(policy); const roomWelcomeMessage = ReportUtils.getRoomWelcomeMessage(report, isUserPolicyAdmin); - const moneyRequestOptions = ReportUtils.temporary_getMoneyRequestOptions(report, policy, otherParticipantsAccountIDs, canUseTrackExpense); + const moneyRequestOptions = ReportUtils.temporary_getMoneyRequestOptions(report, policy, participantAccountIDs, canUseTrackExpense); const additionalText = moneyRequestOptions .filter((item): item is Exclude => item !== CONST.IOU.TYPE.INVOICE) .map((item) => translate(`reportActionsView.iouTypes.${item}`)) diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index db628aa065ef..609b202efb26 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -694,6 +694,7 @@ function createOption( result.isWaitingOnBankAccount = report.isWaitingOnBankAccount; result.policyID = report.policyID; result.isSelfDM = ReportUtils.isSelfDM(report); + result.isOneOnOneChat = ReportUtils.isOneOnOneChat(report); const visibleParticipantAccountIDs = Object.entries(report.participants ?? {}) .filter(([, participant]) => participant && !participant.hidden) @@ -1467,19 +1468,12 @@ function createOptionList(personalDetails: OnyxEntry, repor return; } - const isSelfDM = ReportUtils.isSelfDM(report); - let accountIDs = []; - - if (isSelfDM) { - // For selfDM we need to add the currentUser as participants. - accountIDs = [currentUserAccountID ?? 0]; - } else { - accountIDs = Object.keys(report.participants ?? {}).map(Number); - if (ReportUtils.isOneOnOneChat(report)) { - // For 1:1 chat, we don't want to include currentUser as participants in order to not mark 1:1 chats as having multiple participants - accountIDs = accountIDs.filter((accountID) => accountID !== currentUserAccountID); - } - } + const isOneOnOneChat = ReportUtils.isOneOnOneChat(report); + + // For 1:1 chat, we don't want to include currentUser as participants in order to not mark 1:1 chats as having multiple participants + const accountIDs = Object.keys(report.participants ?? {}) + .map(Number) + .filter((accountID) => !isOneOnOneChat || accountID !== currentUserAccountID); if (!accountIDs || accountIDs.length === 0) { return; @@ -1511,8 +1505,11 @@ function createOptionList(personalDetails: OnyxEntry, repor } function createOptionFromReport(report: Report, personalDetails: OnyxEntry) { - const isSelfDM = ReportUtils.isSelfDM(report); - const accountIDs = isSelfDM ? [currentUserAccountID ?? 0] : Object.keys(report.participants ?? {}).map(Number); + const isOneOnOneChat = ReportUtils.isOneOnOneChat(report); + // For 1:1 chat, we don't want to include currentUser as participants in order to not mark 1:1 chats as having multiple participants + const accountIDs = Object.keys(report.participants ?? {}) + .map(Number) + .filter((accountID) => !isOneOnOneChat || accountID !== currentUserAccountID); return { item: report, @@ -1699,18 +1696,12 @@ function getOptions( const isPolicyExpenseChat = option.isPolicyExpenseChat; const isMoneyRequestReport = option.isMoneyRequestReport; const isSelfDM = option.isSelfDM; - let accountIDs = []; - - if (isSelfDM) { - // For selfDM we need to add the currentUser as participants. - accountIDs = [currentUserAccountID ?? 0]; - } else { - accountIDs = Object.keys(report.participants ?? {}).map(Number); - if (ReportUtils.isOneOnOneChat(report)) { - // For 1:1 chat, we don't want to include currentUser as participants in order to not mark 1:1 chats as having multiple participants - accountIDs = accountIDs.filter((accountID) => accountID !== currentUserAccountID); - } - } + const isOneOnOneChat = option.isOneOnOneChat; + + // For 1:1 chat, we don't want to include currentUser as participants in order to not mark 1:1 chats as having multiple participants + const accountIDs = Object.keys(report.participants ?? {}) + .map(Number) + .filter((accountID) => !isOneOnOneChat || accountID !== currentUserAccountID); if (isPolicyExpenseChat && report.isOwnPolicyExpenseChat && !includeOwnedWorkspaceChats) { return; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 2e2c7f9c4d3f..48110059cd7c 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -440,6 +440,7 @@ type OptionData = { isDisabled?: boolean | null; name?: string | null; isSelfDM?: boolean; + isOneOnOneChat?: boolean; reportID?: string; enabled?: boolean; data?: Partial; @@ -1022,7 +1023,9 @@ function isSystemChat(report: OnyxEntry): boolean { * Only returns true if this is our main 1:1 DM report with Concierge */ function isConciergeChatReport(report: OnyxEntry): boolean { - const participantAccountIDs = Object.keys(report?.participants ?? {}).map(Number); + const participantAccountIDs = Object.keys(report?.participants ?? {}) + .map(Number) + .filter((accountID) => accountID !== currentUserAccountID); return participantAccountIDs.length === 1 && participantAccountIDs[0] === CONST.ACCOUNT_ID.CONCIERGE && !isChatThread(report); } @@ -1277,17 +1280,6 @@ function isPolicyAdmin(policyID: string, policies: OnyxCollection): bool return policyRole === CONST.POLICY.ROLE.ADMIN; } -/** - * Returns true if report has a single participant. - */ -function hasSingleOtherParticipant(report: OnyxEntry): boolean { - return ( - Object.keys(report?.participants ?? {}) - .map(Number) - .filter((accountID) => accountID !== currentUserAccountID).length === 1 - ); -} - /** * Checks whether all the transactions linked to the IOU report are of the Distance Request type with pending routes */ @@ -1400,7 +1392,9 @@ function isOneTransactionThread(reportID: string, parentReportID: string): boole * */ function isOneOnOneChat(report: OnyxEntry): boolean { - const participantAccountIDs = Object.keys(report?.participants ?? {}); + const participantAccountIDs = Object.keys(report?.participants ?? {}) + .map(Number) + .filter((accountID) => accountID !== currentUserAccountID); return ( !isChatRoom(report) && !isExpenseRequest(report) && @@ -1582,7 +1576,7 @@ function getReportRecipientAccountIDs(report: OnyxEntry, currentLoginAcc // get parent report and use its participants array. if (isThread(report) && !(isTaskReport(report) || isMoneyRequestReport(report))) { const parentReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID}`] ?? null; - if (hasSingleOtherParticipant(parentReport)) { + if (isOneOnOneChat(parentReport)) { finalReport = parentReport; } } @@ -2048,6 +2042,13 @@ function getIcons( return icons; } + if (isOneOnOneChat(report)) { + const otherParticipantsAccountIDs = Object.keys(report.participants ?? {}) + .map(Number) + .filter((accountID) => accountID !== currentUserAccountID); + return getIconsForParticipants(otherParticipantsAccountIDs, personalDetails); + } + const participantAccountIDs = Object.keys(report.participants ?? {}).map(Number); return getIconsForParticipants(participantAccountIDs, personalDetails); } @@ -3183,8 +3184,8 @@ function getReportName(report: OnyxEntry, policy: OnyxEntry = nu .map(Number) .filter((accountID) => accountID !== currentUserAccountID) .slice(0, 5); - const hasMultipleOtherParticipants = participantsWithoutCurrentUser.length > 1; - return participantsWithoutCurrentUser.map((accountID) => getDisplayNameForParticipant(accountID, hasMultipleOtherParticipants)).join(', '); + const isMultipleParticipantReport = participantsWithoutCurrentUser.length > 1; + return participantsWithoutCurrentUser.map((accountID) => getDisplayNameForParticipant(accountID, isMultipleParticipantReport)).join(', '); } /** @@ -3265,16 +3266,19 @@ function getParentNavigationSubtitle(report: OnyxEntry): ParentNavigatio * Navigate to the details page of a given report */ function navigateToDetailsPage(report: OnyxEntry) { - if (isSelfDM(report)) { - Navigation.navigate(ROUTES.PROFILE.getRoute(currentUserAccountID ?? 0)); - return; - } + const isSelfDMReport = isSelfDM(report); + const isOneOnOneChatReport = isOneOnOneChat(report); - if (isOneOnOneChat(report)) { - const participantAccountID = Object.keys(report?.participants ?? {})[0]; - Navigation.navigate(ROUTES.PROFILE.getRoute(participantAccountID)); + // For 1:1 chat, we don't want to include currentUser as participants in order to not mark 1:1 chats as having multiple participants + const participantAccountID = Object.keys(report?.participants ?? {}) + .map(Number) + .filter((accountID) => !isOneOnOneChatReport || accountID !== currentUserAccountID); + + if (isSelfDMReport || isOneOnOneChatReport) { + Navigation.navigate(ROUTES.PROFILE.getRoute(participantAccountID[0])); return; } + if (report?.reportID) { Navigation.navigate(ROUTES.REPORT_WITH_ID_DETAILS.getRoute(report?.reportID)); } @@ -3284,11 +3288,18 @@ function navigateToDetailsPage(report: OnyxEntry) { * Go back to the details page of a given report */ function goBackToDetailsPage(report: OnyxEntry) { - if (isOneOnOneChat(report)) { - const participantAccountID = Object.keys(report?.participants ?? {})[0]; - Navigation.goBack(ROUTES.PROFILE.getRoute(participantAccountID)); + const isOneOnOneChatReport = isOneOnOneChat(report); + + // For 1:1 chat, we don't want to include currentUser as participants in order to not mark 1:1 chats as having multiple participants + const participantAccountID = Object.keys(report?.participants ?? {}) + .map(Number) + .filter((accountID) => !isOneOnOneChatReport || accountID !== currentUserAccountID); + + if (isOneOnOneChatReport) { + Navigation.navigate(ROUTES.PROFILE.getRoute(participantAccountID[0])); return; } + Navigation.goBack(ROUTES.REPORT_SETTINGS.getRoute(report?.reportID ?? '')); } @@ -5444,7 +5455,7 @@ function getMoneyRequestOptions(report: OnyxEntry, policy: OnyxEntry currentUserPersonalDetails?.accountID !== accountID); - const hasSingleOtherParticipantInReport = otherParticipants.length === 1; + const hasSingleParticipantInReport = otherParticipants.length === 1; let options: IOUType[] = []; if (isSelfDM(report)) { @@ -5477,7 +5488,7 @@ function getMoneyRequestOptions(report: OnyxEntry, policy: OnyxEntry): bool return false; } - // If we have a DM AND the accountID we are checking is the current user THEN we won't find them as a participant and must assume they are a participant - if (isDM(report) && accountID === currentUserAccountID) { - return true; - } - const possibleAccountIDs = Object.keys(report?.participants ?? {}).map(Number); if (report?.ownerAccountID) { possibleAccountIDs.push(report?.ownerAccountID); @@ -6634,7 +6640,6 @@ export { hasOnlyHeldExpenses, hasOnlyTransactionsWithPendingRoutes, hasReportNameError, - hasSingleOtherParticipant, hasSmartscanError, hasUpdatedTotal, hasViolations, diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index a822c3e00301..eef5767ea1cd 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -48,6 +48,14 @@ Onyx.connect({ }, }); +let currentUserAccountID: number | undefined; +Onyx.connect({ + key: ONYXKEYS.SESSION, + callback: (value) => { + currentUserAccountID = value?.accountID; + }, +}); + function compareStringDates(a: string, b: string): 0 | 1 | -1 { if (a < b) { return -1; @@ -237,12 +245,10 @@ function getOptionData({ isDeletedParentAction: false, }; - let participantAccountIDs = Object.keys(report.participants ?? {}).map(Number); - - // Currently, currentUser is not included in participantAccountIDs, so for selfDM we need to add the currentUser(report owner) as participants. - if (ReportUtils.isSelfDM(report)) { - participantAccountIDs = [report.ownerAccountID ?? 0]; - } + const isOneOnOneChat = ReportUtils.isOneOnOneChat(report); + const participantAccountIDs = Object.keys(report.participants ?? {}) + .map(Number) + .filter((accountID) => !isOneOnOneChat || accountID !== currentUserAccountID); const participantPersonalDetailList = Object.values(OptionsListUtils.getPersonalDetailsForAccountIDs(participantAccountIDs, personalDetails)) as PersonalDetails[]; const personalDetail = participantPersonalDetailList[0] ?? {}; @@ -281,6 +287,7 @@ function getOptionData({ result.chatType = report.chatType; result.isDeletedParentAction = report.isDeletedParentAction; result.isSelfDM = ReportUtils.isSelfDM(report); + result.isOneOnOneChat = isOneOnOneChat; const visibleParticipantAccountIDs = Object.entries(report.participants ?? {}) .filter(([, participant]) => participant && !participant.hidden) diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index e893b9e320bd..ef063281c0e9 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -762,19 +762,18 @@ function getAssignee(assigneeAccountID: number, personalDetails: OnyxEntry, personalDetails: OnyxEntry): ShareDestination { const report = reports?.[`report_${reportID}`] ?? null; - const otherParticipantsAccountIDs = Object.keys(report?.participants ?? {}) + const isOneOnOneChat = ReportUtils.isOneOnOneChat(report); + + const participants = Object.keys(report?.participants ?? {}) .map(Number) - .filter((accountID) => accountID !== currentUserAccountID); + .filter((accountID) => !isOneOnOneChat || accountID !== currentUserAccountID); - const hasMultipleOtherParticipants = otherParticipantsAccountIDs.length > 1; - const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips( - OptionsListUtils.getPersonalDetailsForAccountIDs(otherParticipantsAccountIDs, personalDetails), - hasMultipleOtherParticipants, - ); + const isMultipleParticipant = participants.length > 1; + const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips(OptionsListUtils.getPersonalDetailsForAccountIDs(participants, personalDetails), isMultipleParticipant); let subtitle = ''; - if (ReportUtils.isChatReport(report) && ReportUtils.isDM(report) && ReportUtils.hasSingleOtherParticipant(report)) { - const participantAccountID = otherParticipantsAccountIDs[0] ?? -1; + if (isOneOnOneChat) { + const participantAccountID = participants[0] ?? -1; const displayName = personalDetails?.[participantAccountID]?.displayName ?? ''; const login = personalDetails?.[participantAccountID]?.login ?? ''; diff --git a/src/pages/home/HeaderView.tsx b/src/pages/home/HeaderView.tsx index f6d3d20d75ad..ab96eb652d04 100644 --- a/src/pages/home/HeaderView.tsx +++ b/src/pages/home/HeaderView.tsx @@ -89,19 +89,16 @@ function HeaderView({ const styles = useThemeStyles(); const isSelfDM = ReportUtils.isSelfDM(report); const isGroupChat = ReportUtils.isGroupChat(report) || ReportUtils.isDeprecatedGroupDM(report); + const isOneOnOneChat = ReportUtils.isOneOnOneChat(report); - const allParticipantsAccountIDs = useMemo(() => Object.keys(report.participants ?? {}).map(Number), [report.participants]); - const isMultipleParticipant = allParticipantsAccountIDs.length > 1; + const participants = Object.keys(report?.participants ?? {}) + .map(Number) + .filter((accountID) => !isOneOnOneChat || accountID !== session?.accountID) + .slice(0, 5); + const isMultipleParticipant = participants.length > 1; - const shouldIncludeCurrentUser = isSelfDM || isGroupChat; - const participantsAccountIDs = useMemo( - () => allParticipantsAccountIDs.filter((accountID) => shouldIncludeCurrentUser || accountID !== session?.accountID).slice(0, 5), - [shouldIncludeCurrentUser, allParticipantsAccountIDs, session?.accountID], - ); - - const shouldUseShortFormInTooltip = participantsAccountIDs.length > 1; - const participantPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs(participantsAccountIDs, personalDetails); - const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips(participantPersonalDetails, shouldUseShortFormInTooltip, undefined, isSelfDM); + const participantPersonalDetails = OptionsListUtils.getPersonalDetailsForAccountIDs(participants, personalDetails); + const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips(participantPersonalDetails, isMultipleParticipant, undefined, isSelfDM); const isChatThread = ReportUtils.isChatThread(report); const isChatRoom = ReportUtils.isChatRoom(report); @@ -112,7 +109,7 @@ function HeaderView({ const title = ReportUtils.getReportName(reportHeaderData); const subtitle = ReportUtils.getChatRoomSubtitle(reportHeaderData); const parentNavigationSubtitleData = ReportUtils.getParentNavigationSubtitle(reportHeaderData); - const isConcierge = ReportUtils.hasSingleOtherParticipant(report) && participantsAccountIDs.includes(CONST.ACCOUNT_ID.CONCIERGE); + const isConcierge = ReportUtils.isConciergeChatReport(report); const isCanceledTaskReport = ReportUtils.isCanceledTaskReport(report, parentReportAction); const isWhisperAction = ReportActionsUtils.isWhisperAction(parentReportAction); const isUserCreatedPolicyRoom = ReportUtils.isUserCreatedPolicyRoom(report); From 5338841cc86784484918e54ecf4ed107f4015a9a Mon Sep 17 00:00:00 2001 From: Abdelhafidh Belalia <16493223+s77rt@users.noreply.github.com> Date: Fri, 3 May 2024 04:15:41 +0100 Subject: [PATCH 11/21] Fix getChatByParticipants --- src/components/OptionListContextProvider.tsx | 8 ++++---- src/libs/actions/IOU.ts | 14 ++++++-------- src/libs/actions/Report.ts | 12 +++++------- src/libs/actions/Task.ts | 2 +- src/pages/ProfilePage.tsx | 2 +- 5 files changed, 17 insertions(+), 21 deletions(-) diff --git a/src/components/OptionListContextProvider.tsx b/src/components/OptionListContextProvider.tsx index b530eecd4d3c..3b3406a36ab4 100644 --- a/src/components/OptionListContextProvider.tsx +++ b/src/components/OptionListContextProvider.tsx @@ -123,16 +123,16 @@ function OptionsListContextProvider({reports, children}: OptionsListProviderProp newReportOption: OptionsListUtils.SearchOption; }> = []; - Object.keys(personalDetails).forEach((accoutID) => { - const prevPersonalDetail = prevPersonalDetails?.[accoutID]; - const personalDetail = personalDetails?.[accoutID]; + Object.keys(personalDetails).forEach((accountID) => { + const prevPersonalDetail = prevPersonalDetails?.[accountID]; + const personalDetail = personalDetails?.[accountID]; if (isEqualPersonalDetail(prevPersonalDetail, personalDetail)) { return; } Object.values(reports ?? {}) - .filter((report) => Boolean(report?.participantAccountIDs?.includes(Number(accoutID))) || (ReportUtils.isSelfDM(report) && report?.ownerAccountID === Number(accoutID))) + .filter((report) => Boolean(Object.keys(report?.participants ?? {}).includes(accountID)) || (ReportUtils.isSelfDM(report) && report?.ownerAccountID === Number(accountID))) .forEach((report) => { if (!report) { return; diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 3371c0369dc5..8b2f85cb6dff 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -1771,7 +1771,7 @@ function getMoneyRequestInformation( } if (!chatReport) { - chatReport = ReportUtils.getChatByParticipants([payerAccountID]); + chatReport = ReportUtils.getChatByParticipants([payerAccountID, userAccountID]); } // If we still don't have a report, it likely doens't exist and we need to build an optimistic one @@ -3559,8 +3559,9 @@ function getOrCreateOptimisticSplitChatReport(existingSplitChatReportID: string, // If we do not have one locally then we will search for a chat with the same participants (only for 1:1 chats). const shouldGetOrCreateOneOneDM = participants.length < 2; + const allParticipantsAccountIDs = [...participantAccountIDs, currentUserAccountID]; if (!existingSplitChatReport && shouldGetOrCreateOneOneDM) { - existingSplitChatReport = ReportUtils.getChatByParticipants(participantAccountIDs); + existingSplitChatReport = ReportUtils.getChatByParticipants(allParticipantsAccountIDs); } // We found an existing chat report we are done... @@ -3569,9 +3570,6 @@ function getOrCreateOptimisticSplitChatReport(existingSplitChatReportID: string, return {existingSplitChatReport, splitChatReport: existingSplitChatReport}; } - // No existing chat by this point we need to create it - const allParticipantsAccountIDs = [...participantAccountIDs, currentUserAccountID]; - // Create a Group Chat if we have multiple participants if (participants.length > 1) { const splitChatReport = ReportUtils.buildOptimisticChatReport( @@ -3834,7 +3832,7 @@ function createSplitsAndOnyxData( oneOnOneChatReport = splitChatReport; shouldCreateOptimisticPersonalDetails = !existingSplitChatReport && !personalDetailExists; } else { - const existingChatReport = ReportUtils.getChatByParticipants([accountID]); + const existingChatReport = ReportUtils.getChatByParticipants([accountID, currentUserAccountID]); isNewOneOnOneChatReport = !existingChatReport; shouldCreateOptimisticPersonalDetails = isNewOneOnOneChatReport && !personalDetailExists; oneOnOneChatReport = existingChatReport ?? ReportUtils.buildOptimisticChatReport([accountID]); @@ -4531,7 +4529,7 @@ function completeSplitBill(chatReportID: string, reportAction: OnyxTypes.ReportA // The workspace chat reportID is saved in the splits array when starting a split expense with a workspace oneOnOneChatReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${participant.chatReportID}`] ?? null; } else { - const existingChatReport = ReportUtils.getChatByParticipants(participant.accountID ? [participant.accountID] : []); + const existingChatReport = ReportUtils.getChatByParticipants(participant.accountID ? [participant.accountID, sessionAccountID] : []); isNewOneOnOneChatReport = !existingChatReport; oneOnOneChatReport = existingChatReport ?? ReportUtils.buildOptimisticChatReport(participant.accountID ? [participant.accountID] : []); } @@ -5328,7 +5326,7 @@ function getSendMoneyParams( idempotencyKey: Str.guid(), }); - let chatReport = !isEmptyObject(report) && report?.reportID ? report : ReportUtils.getChatByParticipants([recipientAccountID]); + let chatReport = !isEmptyObject(report) && report?.reportID ? report : ReportUtils.getChatByParticipants([recipientAccountID, userAccountID]); let isNewChat = false; if (!chatReport) { chatReport = ReportUtils.buildOptimisticChatReport([recipientAccountID]); diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 422a0a53b0ab..8acd685571c6 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -897,7 +897,7 @@ function navigateToAndOpenReport( // If we are not creating a new Group Chat then we are creating a 1:1 DM and will look for an existing chat if (!isGroupChat) { - chat = ReportUtils.getChatByParticipants(participantAccountIDs); + chat = ReportUtils.getChatByParticipants([...participantAccountIDs, currentUserAccountID]); } if (isEmptyObject(chat)) { @@ -926,7 +926,7 @@ function navigateToAndOpenReport( */ function navigateToAndOpenReportWithAccountIDs(participantAccountIDs: number[]) { let newChat: ReportUtils.OptimisticChatReport | EmptyObject = {}; - const chat = ReportUtils.getChatByParticipants(participantAccountIDs); + const chat = ReportUtils.getChatByParticipants([...participantAccountIDs, currentUserAccountID]); if (!chat) { newChat = ReportUtils.buildOptimisticChatReport(participantAccountIDs); } @@ -2468,7 +2468,7 @@ function navigateToMostRecentReport(currentReport: OnyxEntry) { Navigation.navigate(lastAccessedReportRoute, CONST.NAVIGATION.TYPE.FORCED_UP); } else { const participantAccountIDs = PersonalDetailsUtils.getAccountIDsByLogins([CONST.EMAIL.CONCIERGE]); - const chat = ReportUtils.getChatByParticipants(participantAccountIDs); + const chat = ReportUtils.getChatByParticipants([...participantAccountIDs, currentUserAccountID]); if (chat?.reportID) { // If it is not a chat thread we should call Navigation.goBack to pop the current route first before navigating to Concierge. if (!isChatThread) { @@ -2644,8 +2644,6 @@ function inviteToRoom(reportID: string, inviteeEmailsToAccountIDs: InvitedEmails onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, value: { - participantAccountIDs: report.participantAccountIDs, - visibleChatMemberAccountIDs: report.visibleChatMemberAccountIDs, participants: inviteeAccountIDs.reduce((revertedParticipants: Record, accountID) => { // eslint-disable-next-line no-param-reassign revertedParticipants[accountID] = null; @@ -2982,7 +2980,7 @@ function completeOnboarding( ) { const targetEmail = CONST.EMAIL.CONCIERGE; const actorAccountID = PersonalDetailsUtils.getAccountIDsByLogins([targetEmail])[0]; - const targetChatReport = ReportUtils.getChatByParticipants([actorAccountID]); + const targetChatReport = ReportUtils.getChatByParticipants([actorAccountID, currentUserAccountID]); const {reportID: targetChatReportID = '', policyID: targetChatPolicyID = ''} = targetChatReport ?? {}; // Mention message @@ -3323,7 +3321,7 @@ function completeEngagementModal(choice: OnboardingPurposeType, text?: string) { lastReadTime: currentTime, }; - const conciergeChatReport = ReportUtils.getChatByParticipants([conciergeAccountID]); + const conciergeChatReport = ReportUtils.getChatByParticipants([conciergeAccountID, currentUserAccountID]); conciergeChatReportID = conciergeChatReport?.reportID; const report = ReportUtils.getReport(conciergeChatReportID); diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index ef063281c0e9..6b2698c38e41 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -661,7 +661,7 @@ function setAssigneeValue( if (!isCurrentUser) { // Check for the chatReport by participants IDs if (!report) { - report = ReportUtils.getChatByParticipants([assigneeAccountID]); + report = ReportUtils.getChatByParticipants([assigneeAccountID, currentUserAccountID]); } // If chat report is still not found we need to build new optimistic chat report if (!report) { diff --git a/src/pages/ProfilePage.tsx b/src/pages/ProfilePage.tsx index 55177a46d320..d5e556b25dd0 100755 --- a/src/pages/ProfilePage.tsx +++ b/src/pages/ProfilePage.tsx @@ -80,7 +80,7 @@ function ProfilePage({route}: ProfilePageProps) { const reportKey = useMemo(() => { const accountID = Number(route.params?.accountID ?? 0); - const reportID = ReportUtils.getChatByParticipants([accountID], reports)?.reportID ?? ''; + const reportID = ReportUtils.getChatByParticipants(session?.accountID ? [accountID, session.accountID] : [], reports)?.reportID ?? ''; if ((Boolean(session) && Number(session?.accountID) === accountID) || SessionActions.isAnonymousUser() || !reportID) { return `${ONYXKEYS.COLLECTION.REPORT}0` as const; From 35ed090b10772af9b4d38e03b1da1e512fa5dd40 Mon Sep 17 00:00:00 2001 From: Abdelhafidh Belalia <16493223+s77rt@users.noreply.github.com> Date: Fri, 3 May 2024 07:31:35 +0100 Subject: [PATCH 12/21] fix more tests --- src/libs/OptionsListUtils.ts | 17 +++++++++++------ src/libs/SidebarUtils.ts | 10 +++++----- src/libs/actions/IOU.ts | 12 ++++++------ src/libs/actions/Report.ts | 5 +++-- src/libs/actions/Task.ts | 2 +- src/pages/NewChatConfirmPage.tsx | 1 + src/pages/home/HeaderView.tsx | 1 + tests/actions/IOUTest.ts | 23 +++++++++++++---------- tests/unit/OptionsListUtilsTest.ts | 22 ++++++++++++++++++---- 9 files changed, 59 insertions(+), 34 deletions(-) diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 609b202efb26..e9326de64658 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -694,13 +694,16 @@ function createOption( result.isWaitingOnBankAccount = report.isWaitingOnBankAccount; result.policyID = report.policyID; result.isSelfDM = ReportUtils.isSelfDM(report); - result.isOneOnOneChat = ReportUtils.isOneOnOneChat(report); + // For 1:1 chat, we don't want to include currentUser as participants in order to not mark 1:1 chats as having multiple participants + const isOneOnOneChat = ReportUtils.isOneOnOneChat(report); const visibleParticipantAccountIDs = Object.entries(report.participants ?? {}) .filter(([, participant]) => participant && !participant.hidden) - .map(([accountID]) => Number(accountID)); + .map(([accountID]) => Number(accountID)) + .filter((accountID) => !isOneOnOneChat || accountID !== currentUserAccountID); result.tooltipText = ReportUtils.getReportParticipantsTitle(visibleParticipantAccountIDs); + result.isOneOnOneChat = isOneOnOneChat; hasMultipleParticipants = personalDetailList.length > 1 || result.isChatRoom || result.isPolicyExpenseChat; subtitle = ReportUtils.getChatRoomSubtitle(report); @@ -765,9 +768,12 @@ function createOption( function getReportOption(participant: Participant): ReportUtils.OptionData { const report = ReportUtils.getReport(participant.reportID); + // For 1:1 chat, we don't want to include currentUser as participants in order to not mark 1:1 chats as having multiple participants + const isOneOnOneChat = ReportUtils.isOneOnOneChat(report); const visibleParticipantAccountIDs = Object.entries(report?.participants ?? {}) .filter(([, reportParticipant]) => reportParticipant && !reportParticipant.hidden) - .map(([accountID]) => Number(accountID)); + .map(([accountID]) => Number(accountID)) + .filter((accountID) => !isOneOnOneChat || accountID !== currentUserAccountID); const option = createOption( visibleParticipantAccountIDs, @@ -1468,9 +1474,8 @@ function createOptionList(personalDetails: OnyxEntry, repor return; } - const isOneOnOneChat = ReportUtils.isOneOnOneChat(report); - // For 1:1 chat, we don't want to include currentUser as participants in order to not mark 1:1 chats as having multiple participants + const isOneOnOneChat = ReportUtils.isOneOnOneChat(report); const accountIDs = Object.keys(report.participants ?? {}) .map(Number) .filter((accountID) => !isOneOnOneChat || accountID !== currentUserAccountID); @@ -1505,8 +1510,8 @@ function createOptionList(personalDetails: OnyxEntry, repor } function createOptionFromReport(report: Report, personalDetails: OnyxEntry) { - const isOneOnOneChat = ReportUtils.isOneOnOneChat(report); // For 1:1 chat, we don't want to include currentUser as participants in order to not mark 1:1 chats as having multiple participants + const isOneOnOneChat = ReportUtils.isOneOnOneChat(report); const accountIDs = Object.keys(report.participants ?? {}) .map(Number) .filter((accountID) => !isOneOnOneChat || accountID !== currentUserAccountID); diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index eef5767ea1cd..407bacc3c3ee 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -245,10 +245,15 @@ function getOptionData({ isDeletedParentAction: false, }; + // For 1:1 chat, we don't want to include currentUser as participants in order to not mark 1:1 chats as having multiple participants const isOneOnOneChat = ReportUtils.isOneOnOneChat(report); const participantAccountIDs = Object.keys(report.participants ?? {}) .map(Number) .filter((accountID) => !isOneOnOneChat || accountID !== currentUserAccountID); + const visibleParticipantAccountIDs = Object.entries(report.participants ?? {}) + .filter(([, participant]) => participant && !participant.hidden) + .map(([accountID]) => Number(accountID)) + .filter((accountID) => !isOneOnOneChat || accountID !== currentUserAccountID); const participantPersonalDetailList = Object.values(OptionsListUtils.getPersonalDetailsForAccountIDs(participantAccountIDs, personalDetails)) as PersonalDetails[]; const personalDetail = participantPersonalDetailList[0] ?? {}; @@ -288,11 +293,6 @@ function getOptionData({ result.isDeletedParentAction = report.isDeletedParentAction; result.isSelfDM = ReportUtils.isSelfDM(report); result.isOneOnOneChat = isOneOnOneChat; - - const visibleParticipantAccountIDs = Object.entries(report.participants ?? {}) - .filter(([, participant]) => participant && !participant.hidden) - .map(([accountID]) => Number(accountID)); - result.tooltipText = ReportUtils.getReportParticipantsTitle(visibleParticipantAccountIDs); const hasMultipleParticipants = participantPersonalDetailList.length > 1 || result.isChatRoom || result.isPolicyExpenseChat || ReportUtils.isExpenseReport(report); diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 8b2f85cb6dff..e57c12b0720d 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -1771,13 +1771,13 @@ function getMoneyRequestInformation( } if (!chatReport) { - chatReport = ReportUtils.getChatByParticipants([payerAccountID, userAccountID]); + chatReport = ReportUtils.getChatByParticipants([payerAccountID, payeeAccountID]); } // If we still don't have a report, it likely doens't exist and we need to build an optimistic one if (!chatReport) { isNewChatReport = true; - chatReport = ReportUtils.buildOptimisticChatReport([payerAccountID]); + chatReport = ReportUtils.buildOptimisticChatReport([payerAccountID, payeeAccountID]); } // STEP 2: Get the Expense/IOU report. If the moneyRequestReportID has been provided, we want to add the transaction to this specific report. @@ -3835,7 +3835,7 @@ function createSplitsAndOnyxData( const existingChatReport = ReportUtils.getChatByParticipants([accountID, currentUserAccountID]); isNewOneOnOneChatReport = !existingChatReport; shouldCreateOptimisticPersonalDetails = isNewOneOnOneChatReport && !personalDetailExists; - oneOnOneChatReport = existingChatReport ?? ReportUtils.buildOptimisticChatReport([accountID]); + oneOnOneChatReport = existingChatReport ?? ReportUtils.buildOptimisticChatReport([accountID, currentUserAccountID]); } // STEP 2: Get existing IOU/Expense report and update its total OR build a new optimistic one @@ -4531,7 +4531,7 @@ function completeSplitBill(chatReportID: string, reportAction: OnyxTypes.ReportA } else { const existingChatReport = ReportUtils.getChatByParticipants(participant.accountID ? [participant.accountID, sessionAccountID] : []); isNewOneOnOneChatReport = !existingChatReport; - oneOnOneChatReport = existingChatReport ?? ReportUtils.buildOptimisticChatReport(participant.accountID ? [participant.accountID] : []); + oneOnOneChatReport = existingChatReport ?? ReportUtils.buildOptimisticChatReport(participant.accountID ? [participant.accountID, sessionAccountID] : []); } let oneOnOneIOUReport: OneOnOneIOUReport = oneOnOneChatReport?.iouReportID ? allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${oneOnOneChatReport.iouReportID}`] : null; @@ -5326,10 +5326,10 @@ function getSendMoneyParams( idempotencyKey: Str.guid(), }); - let chatReport = !isEmptyObject(report) && report?.reportID ? report : ReportUtils.getChatByParticipants([recipientAccountID, userAccountID]); + let chatReport = !isEmptyObject(report) && report?.reportID ? report : ReportUtils.getChatByParticipants([recipientAccountID, managerID]); let isNewChat = false; if (!chatReport) { - chatReport = ReportUtils.buildOptimisticChatReport([recipientAccountID]); + chatReport = ReportUtils.buildOptimisticChatReport([recipientAccountID, managerID]); isNewChat = true; } const optimisticIOUReport = ReportUtils.buildOptimisticIOUReport(recipientAccountID, managerID, amount, chatReport.reportID, currency, true); diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 8acd685571c6..c53efa32a3a9 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -902,9 +902,10 @@ function navigateToAndOpenReport( if (isEmptyObject(chat)) { if (isGroupChat) { + // If we are creating a group chat then participantAccountIDs is expected to contain currentUserAccountID newChat = ReportUtils.buildOptimisticGroupChatReport(participantAccountIDs, reportName ?? '', avatarUri ?? '', optimisticReportID); } else { - newChat = ReportUtils.buildOptimisticChatReport(participantAccountIDs); + newChat = ReportUtils.buildOptimisticChatReport([...participantAccountIDs, currentUserAccountID]); } } const report = isEmptyObject(chat) ? newChat : chat; @@ -928,7 +929,7 @@ function navigateToAndOpenReportWithAccountIDs(participantAccountIDs: number[]) let newChat: ReportUtils.OptimisticChatReport | EmptyObject = {}; const chat = ReportUtils.getChatByParticipants([...participantAccountIDs, currentUserAccountID]); if (!chat) { - newChat = ReportUtils.buildOptimisticChatReport(participantAccountIDs); + newChat = ReportUtils.buildOptimisticChatReport([...participantAccountIDs, currentUserAccountID]); } const report = chat ?? newChat; diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index 6b2698c38e41..cfecd5b5dda2 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -665,7 +665,7 @@ function setAssigneeValue( } // If chat report is still not found we need to build new optimistic chat report if (!report) { - report = ReportUtils.buildOptimisticChatReport([assigneeAccountID]); + report = ReportUtils.buildOptimisticChatReport([assigneeAccountID, currentUserAccountID]); report.isOptimisticReport = true; // When assigning a task to a new user, by default we share the task in their DM diff --git a/src/pages/NewChatConfirmPage.tsx b/src/pages/NewChatConfirmPage.tsx index f25233f9f568..f14bf3525e07 100644 --- a/src/pages/NewChatConfirmPage.tsx +++ b/src/pages/NewChatConfirmPage.tsx @@ -93,6 +93,7 @@ function NewChatConfirmPage({newGroupDraft, allPersonalDetails}: NewChatConfirmP } const logins: string[] = (newGroupDraft.participants ?? []).map((participant) => participant.login); + console.log(logins); Report.navigateToAndOpenReport(logins, true, newGroupDraft.reportName ?? '', newGroupDraft.avatarUri ?? '', fileRef.current, optimisticReportID.current); }; diff --git a/src/pages/home/HeaderView.tsx b/src/pages/home/HeaderView.tsx index ab96eb652d04..b373da71cdbe 100644 --- a/src/pages/home/HeaderView.tsx +++ b/src/pages/home/HeaderView.tsx @@ -91,6 +91,7 @@ function HeaderView({ const isGroupChat = ReportUtils.isGroupChat(report) || ReportUtils.isDeprecatedGroupDM(report); const isOneOnOneChat = ReportUtils.isOneOnOneChat(report); + // For 1:1 chat, we don't want to include currentUser as participants in order to not mark 1:1 chats as having multiple participants const participants = Object.keys(report?.participants ?? {}) .map(Number) .filter((accountID) => !isOneOnOneChat || accountID !== session?.accountID) diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index 82ae6dd414e3..19d7f05ac8ea 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -37,22 +37,25 @@ jest.mock('@src/libs/Navigation/Navigation', () => ({ const CARLOS_EMAIL = 'cmartins@expensifail.com'; const CARLOS_ACCOUNT_ID = 1; -const CARLOS_PARTICIPANT: Participant = {hidden: false}; +const CARLOS_PARTICIPANT: Participant = {hidden: false, role: 'member'}; const JULES_EMAIL = 'jules@expensifail.com'; const JULES_ACCOUNT_ID = 2; -const JULES_PARTICIPANT: Participant = {hidden: false}; +const JULES_PARTICIPANT: Participant = {hidden: false, role: 'member'}; const RORY_EMAIL = 'rory@expensifail.com'; const RORY_ACCOUNT_ID = 3; -const RORY_PARTICIPANT: Participant = {hidden: false}; +const RORY_PARTICIPANT: Participant = {hidden: false, role: 'admin'}; const VIT_EMAIL = 'vit@expensifail.com'; const VIT_ACCOUNT_ID = 4; -const VIT_PARTICIPANT: Participant = {hidden: false}; +const VIT_PARTICIPANT: Participant = {hidden: false, role: 'member'}; OnyxUpdateManager(); describe('actions/IOU', () => { beforeAll(() => { Onyx.init({ keys: ONYXKEYS, + initialKeyStates: { + [ONYXKEYS.SESSION]: {accountID: RORY_ACCOUNT_ID, email: RORY_EMAIL}, + }, }); }); @@ -101,7 +104,7 @@ describe('actions/IOU', () => { expect(iouReport?.notificationPreference).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN); // They should be linked together - expect(chatReport?.participants).toEqual({[CARLOS_ACCOUNT_ID]: CARLOS_PARTICIPANT}); + expect(chatReport?.participants).toEqual({[RORY_ACCOUNT_ID]: RORY_PARTICIPANT, [CARLOS_ACCOUNT_ID]: CARLOS_PARTICIPANT}); expect(chatReport?.iouReportID).toBe(iouReport?.reportID); resolve(); @@ -254,7 +257,7 @@ describe('actions/IOU', () => { let chatReport: OnyxTypes.Report = { reportID: '1234', type: CONST.REPORT.TYPE.CHAT, - participants: {[CARLOS_ACCOUNT_ID]: CARLOS_PARTICIPANT}, + participants: {[RORY_ACCOUNT_ID]: RORY_PARTICIPANT, [CARLOS_ACCOUNT_ID]: CARLOS_PARTICIPANT}, }; const createdAction: OnyxTypes.ReportAction = { reportActionID: NumberUtils.rand64(), @@ -422,7 +425,7 @@ describe('actions/IOU', () => { reportID: chatReportID, type: CONST.REPORT.TYPE.CHAT, iouReportID, - participants: {[CARLOS_ACCOUNT_ID]: CARLOS_PARTICIPANT}, + participants: {[RORY_ACCOUNT_ID]: RORY_PARTICIPANT, [CARLOS_ACCOUNT_ID]: CARLOS_PARTICIPANT}, }; const createdAction: OnyxTypes.ReportAction = { reportActionID: NumberUtils.rand64(), @@ -646,7 +649,7 @@ describe('actions/IOU', () => { expect(iouReport?.notificationPreference).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN); // They should be linked together - expect(chatReport?.participants).toEqual({[CARLOS_ACCOUNT_ID]: CARLOS_PARTICIPANT}); + expect(chatReport?.participants).toEqual({[RORY_ACCOUNT_ID]: RORY_PARTICIPANT, [CARLOS_ACCOUNT_ID]: CARLOS_PARTICIPANT}); expect(chatReport?.iouReportID).toBe(iouReport?.reportID); resolve(); @@ -942,7 +945,7 @@ describe('actions/IOU', () => { let carlosChatReport: OnyxEntry = { reportID: NumberUtils.rand64(), type: CONST.REPORT.TYPE.CHAT, - participants: {[CARLOS_ACCOUNT_ID]: CARLOS_PARTICIPANT}, + participants: {[RORY_ACCOUNT_ID]: RORY_PARTICIPANT, [CARLOS_ACCOUNT_ID]: CARLOS_PARTICIPANT}, }; const carlosCreatedAction: OnyxEntry = { reportActionID: NumberUtils.rand64(), @@ -955,7 +958,7 @@ describe('actions/IOU', () => { reportID: NumberUtils.rand64(), type: CONST.REPORT.TYPE.CHAT, iouReportID: julesIOUReportID, - participants: {[JULES_ACCOUNT_ID]: JULES_PARTICIPANT}, + participants: {[RORY_ACCOUNT_ID]: RORY_PARTICIPANT, [JULES_ACCOUNT_ID]: JULES_PARTICIPANT}, }; const julesChatCreatedAction: OnyxEntry = { reportActionID: NumberUtils.rand64(), diff --git a/tests/unit/OptionsListUtilsTest.ts b/tests/unit/OptionsListUtilsTest.ts index a59894c8c7f4..f2b21229a7cf 100644 --- a/tests/unit/OptionsListUtilsTest.ts +++ b/tests/unit/OptionsListUtilsTest.ts @@ -22,8 +22,9 @@ describe('OptionsListUtils', () => { participants: { 2: {}, 1: {}, + 5: {}, }, - reportName: 'Iron Man, Mister Fantastic', + reportName: 'Iron Man, Mister Fantastic, Invisible Woman', type: CONST.REPORT.TYPE.CHAT, }, '2': { @@ -32,6 +33,7 @@ describe('OptionsListUtils', () => { isPinned: false, reportID: '2', participants: { + 2: {}, 3: {}, }, reportName: 'Spider-Man', @@ -45,6 +47,7 @@ describe('OptionsListUtils', () => { isPinned: true, reportID: '3', participants: { + 2: {}, 1: {}, }, reportName: 'Mister Fantastic', @@ -56,6 +59,7 @@ describe('OptionsListUtils', () => { isPinned: false, reportID: '4', participants: { + 2: {}, 4: {}, }, reportName: 'Black Panther', @@ -67,6 +71,7 @@ describe('OptionsListUtils', () => { isPinned: false, reportID: '5', participants: { + 2: {}, 5: {}, }, reportName: 'Invisible Woman', @@ -78,6 +83,7 @@ describe('OptionsListUtils', () => { isPinned: false, reportID: '6', participants: { + 2: {}, 6: {}, }, reportName: 'Thor', @@ -91,6 +97,7 @@ describe('OptionsListUtils', () => { isPinned: false, reportID: '7', participants: { + 2: {}, 7: {}, }, reportName: 'Captain America', @@ -104,6 +111,7 @@ describe('OptionsListUtils', () => { isPinned: false, reportID: '8', participants: { + 2: {}, 12: {}, }, reportName: 'Silver Surfer', @@ -117,6 +125,7 @@ describe('OptionsListUtils', () => { isPinned: false, reportID: '9', participants: { + 2: {}, 8: {}, }, reportName: 'Mister Sinister', @@ -223,6 +232,7 @@ describe('OptionsListUtils', () => { isPinned: false, reportID: '11', participants: { + 2: {}, 999: {}, }, reportName: 'Concierge', @@ -238,6 +248,7 @@ describe('OptionsListUtils', () => { isPinned: false, reportID: '12', participants: { + 2: {}, 1000: {}, }, reportName: 'Chronos', @@ -253,6 +264,7 @@ describe('OptionsListUtils', () => { isPinned: false, reportID: '13', participants: { + 2: {}, 1001: {}, }, reportName: 'Receipts', @@ -268,6 +280,7 @@ describe('OptionsListUtils', () => { isPinned: false, reportID: '14', participants: { + 2: {}, 1: {}, 10: {}, 3: {}, @@ -288,6 +301,7 @@ describe('OptionsListUtils', () => { isPinned: false, reportID: '15', participants: { + 2: {}, 3: {}, 4: {}, }, @@ -406,7 +420,7 @@ describe('OptionsListUtils', () => { // Value with latest lastVisibleActionCreated should be at the top. expect(results.recentReports.length).toBe(2); expect(results.recentReports[0].text).toBe('Mister Fantastic'); - expect(results.recentReports[1].text).toBe('Mister Fantastic'); + expect(results.recentReports[1].text).toBe('Mister Fantastic, Invisible Woman'); return waitForBatchedUpdates() .then(() => Onyx.set(ONYXKEYS.PERSONAL_DETAILS_LIST, PERSONAL_DETAILS_WITH_PERIODS)) @@ -2672,7 +2686,7 @@ describe('OptionsListUtils', () => { expect(filteredOptions.recentReports[0].text).toBe('Invisible Woman'); expect(filteredOptions.recentReports[1].text).toBe('Spider-Man'); expect(filteredOptions.recentReports[2].text).toBe('Black Widow'); - expect(filteredOptions.recentReports[3].text).toBe('Mister Fantastic'); + expect(filteredOptions.recentReports[3].text).toBe('Mister Fantastic, Invisible Woman'); expect(filteredOptions.recentReports[4].text).toBe("SHIELD's workspace (archived)"); }); @@ -2745,7 +2759,7 @@ describe('OptionsListUtils', () => { expect(filteredOptions.recentReports.length).toBe(2); expect(filteredOptions.recentReports[0].text).toBe('Mister Fantastic'); - expect(filteredOptions.recentReports[1].text).toBe('Mister Fantastic'); + expect(filteredOptions.recentReports[1].text).toBe('Mister Fantastic, Invisible Woman'); }); }); }); From a4853b6cc15771bd95fa1a5059262fa0a5881276 Mon Sep 17 00:00:00 2001 From: Abdelhafidh Belalia <16493223+s77rt@users.noreply.github.com> Date: Fri, 3 May 2024 15:57:35 +0100 Subject: [PATCH 13/21] Fix IOUTest and lint --- src/pages/NewChatConfirmPage.tsx | 1 - tests/actions/IOUTest.ts | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pages/NewChatConfirmPage.tsx b/src/pages/NewChatConfirmPage.tsx index f14bf3525e07..f25233f9f568 100644 --- a/src/pages/NewChatConfirmPage.tsx +++ b/src/pages/NewChatConfirmPage.tsx @@ -93,7 +93,6 @@ function NewChatConfirmPage({newGroupDraft, allPersonalDetails}: NewChatConfirmP } const logins: string[] = (newGroupDraft.participants ?? []).map((participant) => participant.login); - console.log(logins); Report.navigateToAndOpenReport(logins, true, newGroupDraft.reportName ?? '', newGroupDraft.avatarUri ?? '', fileRef.current, optimisticReportID.current); }; diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index 19d7f05ac8ea..118aed0ae9b1 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -1136,7 +1136,9 @@ describe('actions/IOU', () => { // 5. The chat report with Rory + Vit (new) vitChatReport = Object.values(allReports ?? {}).find( - (report) => report?.type === CONST.REPORT.TYPE.CHAT && isEqual(report.participants, {[VIT_ACCOUNT_ID]: VIT_PARTICIPANT}), + (report) => + report?.type === CONST.REPORT.TYPE.CHAT && + isEqual(report.participants, {[RORY_ACCOUNT_ID]: RORY_PARTICIPANT, [VIT_ACCOUNT_ID]: VIT_PARTICIPANT}), ) ?? null; expect(isEmptyObject(vitChatReport)).toBe(false); expect(vitChatReport?.pendingFields).toStrictEqual({createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD}); From e56163ffce569234b84f39a2d6bc87dc9b2e0c5b Mon Sep 17 00:00:00 2001 From: Abdelhafidh Belalia <16493223+s77rt@users.noreply.github.com> Date: Fri, 3 May 2024 16:21:50 +0100 Subject: [PATCH 14/21] Onyx migration: add current user as a participant --- src/libs/migrations/Participants.ts | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/src/libs/migrations/Participants.ts b/src/libs/migrations/Participants.ts index 0e0cd8722348..99061cceb60c 100644 --- a/src/libs/migrations/Participants.ts +++ b/src/libs/migrations/Participants.ts @@ -22,8 +22,20 @@ function getReports(): Promise> { }); } +function getCurrentUserAccountID(): Promise { + return new Promise((resolve) => { + const connectionID = Onyx.connect({ + key: ONYXKEYS.SESSION, + callback: (session) => { + Onyx.disconnect(connectionID); + return resolve(session?.accountID); + }, + }); + }); +} + export default function (): Promise { - return getReports().then((reports) => { + return Promise.all([getCurrentUserAccountID(), getReports()]).then(([currentUserAccountID, reports]) => { if (!reports) { Log.info('[Migrate Onyx] Skipped Participants migration because there are no reports'); return; @@ -43,6 +55,11 @@ export default function (): Promise { return reportParticipants; }, {}); + // Current user is always a participant + if (currentUserAccountID !== undefined && !participants[currentUserAccountID]) { + participants[currentUserAccountID] = {hidden: false}; + } + // eslint-disable-next-line no-param-reassign reportsCollection[onyxKey as ReportKey] = { participants, From 2733e474fc11093c03fa4c61677e2fac17c7a799 Mon Sep 17 00:00:00 2001 From: Abdelhafidh Belalia <16493223+s77rt@users.noreply.github.com> Date: Sat, 4 May 2024 21:42:29 +0100 Subject: [PATCH 15/21] Clear optimistic participants on OpenReport --- src/libs/actions/Report.ts | 52 ++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 5788a93c36eb..e424be34196d 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -834,43 +834,51 @@ function openReport( key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, value: {[optimisticCreatedAction.reportActionID]: optimisticCreatedAction}, }); - successData.push( - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, - value: {[optimisticCreatedAction.reportActionID]: {pendingAction: null}}, - }, - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, - value: { - pendingFields: { - createChat: null, - }, - errorFields: { - createChat: null, - }, - isOptimisticReport: false, - }, - }, - ); + successData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, + value: {[optimisticCreatedAction.reportActionID]: {pendingAction: null}}, + }); // Add optimistic personal details for new participants const optimisticPersonalDetails: OnyxCollection = {}; const settledPersonalDetails: OnyxCollection = {}; + const redundantParticipants: Record = {}; const participantAccountIDs = PersonalDetailsUtils.getAccountIDsByLogins(participantLoginList); participantLoginList.forEach((login, index) => { const accountID = participantAccountIDs[index]; + const isOptimisticAccount = !allPersonalDetails?.[accountID]; + + if (!isOptimisticAccount) { + return; + } - optimisticPersonalDetails[accountID] = allPersonalDetails?.[accountID] ?? { + optimisticPersonalDetails[accountID] = { login, accountID, avatar: UserUtils.getDefaultAvatarURL(accountID), displayName: login, isOptimisticPersonalDetail: true, }; + settledPersonalDetails[accountID] = null; - settledPersonalDetails[accountID] = allPersonalDetails?.[accountID] ?? null; + // BE will send different participants. We should clear the optimistic ones to avoid duplicated entries + redundantParticipants[accountID] = null; + }); + + successData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, + value: { + participants: redundantParticipants, + pendingFields: { + createChat: null, + }, + errorFields: { + createChat: null, + }, + isOptimisticReport: false, + }, }); optimisticData.push({ From 42317575a4d296a9b16cdd75dff2c94ad15ef0a8 Mon Sep 17 00:00:00 2001 From: Abdelhafidh Belalia <16493223+s77rt@users.noreply.github.com> Date: Sun, 5 May 2024 01:36:29 +0100 Subject: [PATCH 16/21] Clear optimistic participants on IOU --- src/libs/actions/IOU.ts | 140 +++++++++++++++++++++++++++++-------- src/libs/actions/Report.ts | 2 +- 2 files changed, 113 insertions(+), 29 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index e57c12b0720d..1e97e6066e2a 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -458,6 +458,7 @@ function buildOnyxDataForMoneyRequest( const outstandingChildRequest = ReportUtils.getOutstandingChildRequest(iouReport); const clearedPendingFields = Object.fromEntries(Object.keys(transaction.pendingFields ?? {}).map((key) => [key, null])); const optimisticData: OnyxUpdate[] = []; + const successData: OnyxUpdate[] = []; let newQuickAction: ValueOf = isScanRequest ? CONST.QUICK_ACTIONS.REQUEST_SCAN : CONST.QUICK_ACTIONS.REQUEST_MANUAL; if (TransactionUtils.isDistanceRequest(transaction)) { newQuickAction = CONST.QUICK_ACTIONS.REQUEST_DISTANCE; @@ -584,12 +585,27 @@ function buildOnyxDataForMoneyRequest( }); } + const redundantParticipants: Record = {}; if (!isEmptyObject(optimisticPersonalDetailListAction)) { + const successPersonalDetailListAction: Record = {}; + + // BE will send different participants. We clear the optimistic ones to avoid duplicated entries + Object.keys(optimisticPersonalDetailListAction).forEach((accountIDKey) => { + const accountID = Number(accountIDKey); + successPersonalDetailListAction[accountID] = null; + redundantParticipants[accountID] = null; + }); + optimisticData.push({ onyxMethod: Onyx.METHOD.MERGE, key: ONYXKEYS.PERSONAL_DETAILS_LIST, value: optimisticPersonalDetailListAction, }); + successData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.PERSONAL_DETAILS_LIST, + value: successPersonalDetailListAction, + }); } if (!isEmptyObject(optimisticNextStep)) { @@ -600,13 +616,12 @@ function buildOnyxDataForMoneyRequest( }); } - const successData: OnyxUpdate[] = []; - if (isNewChatReport) { successData.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport?.reportID}`, value: { + participants: redundantParticipants, pendingFields: null, errorFields: null, isOptimisticReport: false, @@ -619,16 +634,20 @@ function buildOnyxDataForMoneyRequest( onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`, value: { + participants: redundantParticipants, pendingFields: null, errorFields: null, + isOptimisticReport: false, }, }, { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReport.reportID}`, value: { + participants: redundantParticipants, pendingFields: null, errorFields: null, + isOptimisticReport: false, }, }, { @@ -881,6 +900,7 @@ function buildOnyxDataForInvoice( value: null, }, ]; + const successData: OnyxUpdate[] = []; if (chatReport) { optimisticData.push({ @@ -913,29 +933,48 @@ function buildOnyxDataForInvoice( }); } + const redundantParticipants: Record = {}; if (!isEmptyObject(optimisticPersonalDetailListAction)) { + const successPersonalDetailListAction: Record = {}; + + // BE will send different participants. We clear the optimistic ones to avoid duplicated entries + Object.keys(optimisticPersonalDetailListAction).forEach((accountIDKey) => { + const accountID = Number(accountIDKey); + successPersonalDetailListAction[accountID] = null; + redundantParticipants[accountID] = null; + }); + optimisticData.push({ onyxMethod: Onyx.METHOD.MERGE, key: ONYXKEYS.PERSONAL_DETAILS_LIST, value: optimisticPersonalDetailListAction, }); + successData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.PERSONAL_DETAILS_LIST, + value: successPersonalDetailListAction, + }); } - const successData: OnyxUpdate[] = [ + successData.push( { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`, value: { + participants: redundantParticipants, pendingFields: null, errorFields: null, + isOptimisticReport: false, }, }, { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReport.reportID}`, value: { + participants: redundantParticipants, pendingFields: null, errorFields: null, + isOptimisticReport: false, }, }, { @@ -987,13 +1026,14 @@ function buildOnyxDataForInvoice( }, }, }, - ]; + ); if (isNewChatReport) { successData.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport?.reportID}`, value: { + participants: redundantParticipants, pendingFields: null, errorFields: null, isOptimisticReport: false, @@ -3725,7 +3765,6 @@ function createSplitsAndOnyxData( value: null, }, ]; - const successData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, @@ -3742,11 +3781,12 @@ function createSplitsAndOnyxData( }, ]; + const redundantParticipants: Record = {}; if (!existingSplitChatReport) { successData.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${splitChatReport.reportID}`, - value: {pendingFields: {createChat: null}}, + value: {pendingFields: {createChat: null}, participants: redundantParticipants}, }); } @@ -3906,6 +3946,11 @@ function createSplitsAndOnyxData( } : {}; + if (shouldCreateOptimisticPersonalDetails) { + // BE will send different participants. We clear the optimistic ones to avoid duplicated entries + redundantParticipants[accountID] = null; + } + let oneOnOneReportPreviewAction = getReportPreviewAction(oneOnOneChatReport.reportID, oneOnOneIOUReport.reportID); if (oneOnOneReportPreviewAction) { oneOnOneReportPreviewAction = ReportUtils.updateReportPreview(oneOnOneIOUReport, oneOnOneReportPreviewAction); @@ -4264,11 +4309,12 @@ function startSplitBill({ }, ]; + const redundantParticipants: Record = {}; if (!existingSplitChatReport) { successData.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${splitChatReport.reportID}`, - value: {pendingFields: {createChat: null}}, + value: {pendingFields: {createChat: null}, participants: redundantParticipants}, }); } @@ -4357,6 +4403,8 @@ function startSplitBill({ }, }, }); + // BE will send different participants. We clear the optimistic ones to avoid duplicated entries + redundantParticipants[accountID] = null; } splits.push({ @@ -5430,7 +5478,61 @@ function getSendMoneyParams( }, }; - const successData: OnyxUpdate[] = [ + const successData: OnyxUpdate[] = []; + + // Add optimistic personal details for recipient + let optimisticPersonalDetailListData: OnyxUpdate | EmptyObject = {}; + const optimisticPersonalDetailListAction = isNewChat + ? { + [recipientAccountID]: { + accountID: recipientAccountID, + avatar: UserUtils.getDefaultAvatarURL(recipient.accountID), + // Disabling this line since participant.displayName can be an empty string + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + displayName: recipient.displayName || recipient.login, + login: recipient.login, + }, + } + : {}; + + const redundantParticipants: Record = {}; + if (!isEmptyObject(optimisticPersonalDetailListAction)) { + const successPersonalDetailListAction: Record = {}; + + // BE will send different participants. We clear the optimistic ones to avoid duplicated entries + Object.keys(optimisticPersonalDetailListAction).forEach((accountIDKey) => { + const accountID = Number(accountIDKey); + successPersonalDetailListAction[accountID] = null; + redundantParticipants[accountID] = null; + }); + + optimisticPersonalDetailListData = { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.PERSONAL_DETAILS_LIST, + value: optimisticPersonalDetailListAction, + }; + successData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.PERSONAL_DETAILS_LIST, + value: successPersonalDetailListAction, + }); + } + + successData.push( + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${optimisticIOUReport.reportID}`, + value: { + participants: redundantParticipants, + }, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${optimisticTransactionThread.reportID}`, + value: { + participants: redundantParticipants, + }, + }, { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${optimisticIOUReport.reportID}`, @@ -5463,7 +5565,7 @@ function getSendMoneyParams( }, }, }, - ]; + ); const failureData: OnyxUpdate[] = [ { @@ -5493,14 +5595,12 @@ function getSendMoneyParams( }, ]; - let optimisticPersonalDetailListData: OnyxUpdate | EmptyObject = {}; - // Now, let's add the data we need just when we are creating a new chat report if (isNewChat) { successData.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, - value: {pendingFields: null}, + value: {pendingFields: null, participants: redundantParticipants}, }); failureData.push( { @@ -5523,22 +5623,6 @@ function getSendMoneyParams( }, ); - // Add optimistic personal details for recipient - optimisticPersonalDetailListData = { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.PERSONAL_DETAILS_LIST, - value: { - [recipientAccountID]: { - accountID: recipientAccountID, - avatar: UserUtils.getDefaultAvatarURL(recipient.accountID), - // Disabling this line since participant.displayName can be an empty string - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - displayName: recipient.displayName || recipient.login, - login: recipient.login, - }, - }, - }; - if (optimisticChatReportActionsData.value) { // Add an optimistic created action to the optimistic chat reportActions data optimisticChatReportActionsData.value[optimisticCreatedActionForChat.reportActionID] = optimisticCreatedActionForChat; diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index e424be34196d..82d13bb3bf37 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -862,7 +862,7 @@ function openReport( }; settledPersonalDetails[accountID] = null; - // BE will send different participants. We should clear the optimistic ones to avoid duplicated entries + // BE will send different participants. We clear the optimistic ones to avoid duplicated entries redundantParticipants[accountID] = null; }); From a91f31023d160748450b3f67927579fab6e38861 Mon Sep 17 00:00:00 2001 From: Abdelhafidh Belalia <16493223+s77rt@users.noreply.github.com> Date: Sun, 5 May 2024 03:21:10 +0100 Subject: [PATCH 17/21] Clear optimistic participants on Task --- src/libs/ReportUtils.ts | 18 ++++++++++-------- src/libs/actions/Task.ts | 12 ++++++++++-- 2 files changed, 20 insertions(+), 10 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 48110059cd7c..2bdb66a4d134 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -4730,14 +4730,14 @@ function buildOptimisticTaskReport( policyID: string = CONST.POLICY.OWNER_EMAIL_FAKE, notificationPreference: NotificationPreference = CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS, ): OptimisticTaskReport { - const participants: Participants = - assigneeAccountID && assigneeAccountID !== ownerAccountID - ? { - [assigneeAccountID]: { - hidden: false, - }, - } - : {}; + const participants: Participants = { + [ownerAccountID]: { + hidden: false, + }, + [assigneeAccountID]: { + hidden: false, + }, + }; return { reportID: generateReportID(), @@ -5818,6 +5818,8 @@ function getTaskAssigneeChatOnyxData( createChat: null, }, isOptimisticReport: false, + // BE will send a different participant. We clear the optimistic one to avoid duplicated entries + participants: {[assigneeAccountID]: null}, }, }); diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index cfecd5b5dda2..f65113b948d6 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -1,4 +1,4 @@ -import type {OnyxCollection, OnyxEntry, OnyxUpdate} from 'react-native-onyx'; +import type {NullishDeep, OnyxCollection, OnyxEntry, OnyxUpdate} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import * as Expensicons from '@components/Icon/Expensicons'; @@ -180,6 +180,8 @@ function createTaskAndNavigate( managerID: null, }, isOptimisticReport: false, + // BE will send a different participant. We clear the optimistic one to avoid duplicated entries + participants: {[assigneeAccountID]: null}, }, }, { @@ -529,6 +531,7 @@ function editTaskAssignee( ? CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS : CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, }; + const successReport: NullishDeep = {pendingFields: {...(assigneeAccountID && {managerID: null})}}; const optimisticData: OnyxUpdate[] = [ { @@ -552,7 +555,7 @@ function editTaskAssignee( { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, - value: {pendingFields: {...(assigneeAccountID && {managerID: null})}}, + value: successReport, }, ]; @@ -584,6 +587,11 @@ function editTaskAssignee( assigneeChatReport, ); + if (assigneeChatReport?.isOptimisticReport && assigneeChatReport.pendingFields?.createChat !== CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD) { + // BE will send a different participant. We clear the optimistic one to avoid duplicated entries + successReport.participants = {[assigneeAccountID]: null}; + } + optimisticData.push(...assigneeChatReportOnyxData.optimisticData); successData.push(...assigneeChatReportOnyxData.successData); failureData.push(...assigneeChatReportOnyxData.failureData); From e247a3b0187d9953234ce90a16ac9c7a91c0bcd0 Mon Sep 17 00:00:00 2001 From: Abdelhafidh Belalia <16493223+s77rt@users.noreply.github.com> Date: Sun, 5 May 2024 04:14:45 +0100 Subject: [PATCH 18/21] Fix IOUTest --- tests/actions/IOUTest.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index 118aed0ae9b1..67525f2e2318 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -55,6 +55,7 @@ describe('actions/IOU', () => { keys: ONYXKEYS, initialKeyStates: { [ONYXKEYS.SESSION]: {accountID: RORY_ACCOUNT_ID, email: RORY_EMAIL}, + [ONYXKEYS.PERSONAL_DETAILS_LIST]: {[RORY_ACCOUNT_ID]: {accountID: RORY_ACCOUNT_ID, login: RORY_EMAIL}}, }, }); }); From 8d43462135a8be9c93f34b3c19006d0dcfc5113a Mon Sep 17 00:00:00 2001 From: Abdelhafidh Belalia <16493223+s77rt@users.noreply.github.com> Date: Tue, 7 May 2024 03:11:42 +0100 Subject: [PATCH 19/21] Onyx migration: correct reports by removing invalid participants --- src/libs/migrations/Participants.ts | 34 +++++++++++++++++------------ 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/src/libs/migrations/Participants.ts b/src/libs/migrations/Participants.ts index 99061cceb60c..3dbbef486d68 100644 --- a/src/libs/migrations/Participants.ts +++ b/src/libs/migrations/Participants.ts @@ -3,7 +3,7 @@ import type {NullishDeep, OnyxCollection} from 'react-native-onyx'; import Log from '@libs/Log'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Report} from '@src/types/onyx'; -import type {Participant, Participants} from '@src/types/onyx/Report'; +import type {Participants} from '@src/types/onyx/Report'; type ReportKey = `${typeof ONYXKEYS.COLLECTION.REPORT}${string}`; type OldReport = Report & {participantAccountIDs?: number[]; visibleChatMemberAccountIDs?: number[]}; @@ -44,21 +44,27 @@ export default function (): Promise { const collection = Object.entries(reports).reduce((reportsCollection, [onyxKey, report]) => { // If we have participantAccountIDs then this report is eligible for migration if (report?.participantAccountIDs) { - const visibleParticipants = new Set(report.visibleChatMemberAccountIDs); - const participants = report.participantAccountIDs.reduce((reportParticipants, accountID) => { - const participant: Participant = { - hidden: !visibleParticipants.has(accountID), - }; + const participants: NullishDeep = {}; - // eslint-disable-next-line no-param-reassign - reportParticipants[accountID] = participant; - return reportParticipants; - }, {}); + const deprecatedParticipants = new Set(report.participantAccountIDs); + const deprecatedVisibleParticipants = new Set(report.visibleChatMemberAccountIDs); - // Current user is always a participant - if (currentUserAccountID !== undefined && !participants[currentUserAccountID]) { - participants[currentUserAccountID] = {hidden: false}; - } + // Check all possible participants because some of these may be invalid https://github.com/Expensify/App/pull/40254#issuecomment-2096867084 + const possibleParticipants = new Set([ + ...report.participantAccountIDs, + ...Object.keys(report.participants ?? {}).map(Number), + ...(currentUserAccountID !== undefined ? [currentUserAccountID] : []), + ]); + + possibleParticipants.forEach((accountID) => { + if (deprecatedParticipants.has(accountID) || accountID === currentUserAccountID) { + participants[accountID] = { + hidden: report.participants?.[accountID]?.hidden ?? (!deprecatedVisibleParticipants.has(accountID) && accountID !== currentUserAccountID), + }; + } else { + participants[accountID] = null; + } + }); // eslint-disable-next-line no-param-reassign reportsCollection[onyxKey as ReportKey] = { From 30e119ed9b0c51aa3dacfab4ef3e05dc7ef501e7 Mon Sep 17 00:00:00 2001 From: Abdelhafidh Belalia <16493223+s77rt@users.noreply.github.com> Date: Tue, 7 May 2024 17:31:03 +0100 Subject: [PATCH 20/21] Add assigneeAccountID to participants only if it's set --- src/libs/ReportUtils.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index c852460201a9..43a054bbb3ee 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -4771,11 +4771,12 @@ function buildOptimisticTaskReport( [ownerAccountID]: { hidden: false, }, - [assigneeAccountID]: { - hidden: false, - }, }; + if (assigneeAccountID) { + participants[assigneeAccountID] = {hidden: false}; + } + return { reportID: generateReportID(), reportName: title, From 1d27719c98d81665ad30511ce1503994d6a0cdac Mon Sep 17 00:00:00 2001 From: Abdelhafidh Belalia <16493223+s77rt@users.noreply.github.com> Date: Mon, 13 May 2024 10:38:14 +0100 Subject: [PATCH 21/21] Make filter condition more readable --- src/components/ReportWelcomeText.tsx | 2 +- src/libs/OptionsListUtils.ts | 10 +++++----- src/libs/ReportUtils.ts | 4 ++-- src/libs/SidebarUtils.ts | 4 ++-- src/libs/actions/Task.ts | 2 +- src/pages/home/HeaderView.tsx | 2 +- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/components/ReportWelcomeText.tsx b/src/components/ReportWelcomeText.tsx index 27a45c09c811..717b209c078d 100644 --- a/src/components/ReportWelcomeText.tsx +++ b/src/components/ReportWelcomeText.tsx @@ -45,7 +45,7 @@ function ReportWelcomeText({report, policy, personalDetails}: ReportWelcomeTextP const isDefault = !(isChatRoom || isPolicyExpenseChat || isSelfDM || isInvoiceRoom); const participantAccountIDs = Object.keys(report?.participants ?? {}) .map(Number) - .filter((accountID) => !isOneOnOneChat || accountID !== session?.accountID); + .filter((accountID) => accountID !== session?.accountID || !isOneOnOneChat); const isMultipleParticipant = participantAccountIDs.length > 1; const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips(OptionsListUtils.getPersonalDetailsForAccountIDs(participantAccountIDs, personalDetails), isMultipleParticipant); const isUserPolicyAdmin = PolicyUtils.isPolicyAdmin(policy); diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index 4f488e11dd8d..7f66ff428b54 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -718,7 +718,7 @@ function createOption( const visibleParticipantAccountIDs = Object.entries(report.participants ?? {}) .filter(([, participant]) => participant && !participant.hidden) .map(([accountID]) => Number(accountID)) - .filter((accountID) => !isOneOnOneChat || accountID !== currentUserAccountID); + .filter((accountID) => accountID !== currentUserAccountID || !isOneOnOneChat); result.tooltipText = ReportUtils.getReportParticipantsTitle(visibleParticipantAccountIDs); result.isOneOnOneChat = isOneOnOneChat; @@ -790,7 +790,7 @@ function getReportOption(participant: Participant): ReportUtils.OptionData { const visibleParticipantAccountIDs = Object.entries(report?.participants ?? {}) .filter(([, reportParticipant]) => reportParticipant && !reportParticipant.hidden) .map(([accountID]) => Number(accountID)) - .filter((accountID) => !isOneOnOneChat || accountID !== currentUserAccountID); + .filter((accountID) => accountID !== currentUserAccountID || !isOneOnOneChat); const option = createOption( visibleParticipantAccountIDs, @@ -1491,7 +1491,7 @@ function createOptionList(personalDetails: OnyxEntry, repor const isOneOnOneChat = ReportUtils.isOneOnOneChat(report); const accountIDs = Object.keys(report.participants ?? {}) .map(Number) - .filter((accountID) => !isOneOnOneChat || accountID !== currentUserAccountID); + .filter((accountID) => accountID !== currentUserAccountID || !isOneOnOneChat); if (!accountIDs || accountIDs.length === 0) { return; @@ -1527,7 +1527,7 @@ function createOptionFromReport(report: Report, personalDetails: OnyxEntry !isOneOnOneChat || accountID !== currentUserAccountID); + .filter((accountID) => accountID !== currentUserAccountID || !isOneOnOneChat); return { item: report, @@ -1788,7 +1788,7 @@ function getOptions( // For 1:1 chat, we don't want to include currentUser as participants in order to not mark 1:1 chats as having multiple participants const accountIDs = Object.keys(report.participants ?? {}) .map(Number) - .filter((accountID) => !isOneOnOneChat || accountID !== currentUserAccountID); + .filter((accountID) => accountID !== currentUserAccountID || !isOneOnOneChat); if (isPolicyExpenseChat && report.isOwnPolicyExpenseChat && !includeOwnedWorkspaceChats) { return; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index a765ecac49cc..998a4b3f7859 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3307,7 +3307,7 @@ function navigateToDetailsPage(report: OnyxEntry) { // For 1:1 chat, we don't want to include currentUser as participants in order to not mark 1:1 chats as having multiple participants const participantAccountID = Object.keys(report?.participants ?? {}) .map(Number) - .filter((accountID) => !isOneOnOneChatReport || accountID !== currentUserAccountID); + .filter((accountID) => accountID !== currentUserAccountID || !isOneOnOneChatReport); if (isSelfDMReport || isOneOnOneChatReport) { Navigation.navigate(ROUTES.PROFILE.getRoute(participantAccountID[0])); @@ -3328,7 +3328,7 @@ function goBackToDetailsPage(report: OnyxEntry) { // For 1:1 chat, we don't want to include currentUser as participants in order to not mark 1:1 chats as having multiple participants const participantAccountID = Object.keys(report?.participants ?? {}) .map(Number) - .filter((accountID) => !isOneOnOneChatReport || accountID !== currentUserAccountID); + .filter((accountID) => accountID !== currentUserAccountID || !isOneOnOneChatReport); if (isOneOnOneChatReport) { Navigation.navigate(ROUTES.PROFILE.getRoute(participantAccountID[0])); diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index b00ecfcbc2be..b4ae942547a1 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -245,11 +245,11 @@ function getOptionData({ const isOneOnOneChat = ReportUtils.isOneOnOneChat(report); const participantAccountIDs = Object.keys(report.participants ?? {}) .map(Number) - .filter((accountID) => !isOneOnOneChat || accountID !== currentUserAccountID); + .filter((accountID) => accountID !== currentUserAccountID || !isOneOnOneChat); const visibleParticipantAccountIDs = Object.entries(report.participants ?? {}) .filter(([, participant]) => participant && !participant.hidden) .map(([accountID]) => Number(accountID)) - .filter((accountID) => !isOneOnOneChat || accountID !== currentUserAccountID); + .filter((accountID) => accountID !== currentUserAccountID || !isOneOnOneChat); const participantPersonalDetailList = Object.values(OptionsListUtils.getPersonalDetailsForAccountIDs(participantAccountIDs, personalDetails)) as PersonalDetails[]; const personalDetail = participantPersonalDetailList[0] ?? {}; diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index 323e4df91fe2..9698af6965e3 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -776,7 +776,7 @@ function getShareDestination(reportID: string, reports: OnyxCollection !isOneOnOneChat || accountID !== currentUserAccountID); + .filter((accountID) => accountID !== currentUserAccountID || !isOneOnOneChat); const isMultipleParticipant = participants.length > 1; const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips(OptionsListUtils.getPersonalDetailsForAccountIDs(participants, personalDetails), isMultipleParticipant); diff --git a/src/pages/home/HeaderView.tsx b/src/pages/home/HeaderView.tsx index b373da71cdbe..b7129230ce79 100644 --- a/src/pages/home/HeaderView.tsx +++ b/src/pages/home/HeaderView.tsx @@ -94,7 +94,7 @@ function HeaderView({ // For 1:1 chat, we don't want to include currentUser as participants in order to not mark 1:1 chats as having multiple participants const participants = Object.keys(report?.participants ?? {}) .map(Number) - .filter((accountID) => !isOneOnOneChat || accountID !== session?.accountID) + .filter((accountID) => accountID !== session?.accountID || !isOneOnOneChat) .slice(0, 5); const isMultipleParticipant = participants.length > 1;