diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index 9efff6f2fdb7..0dc12c720f31 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -322,9 +322,9 @@ function getSearchText(report, reportName, personalDetailList, isChatRoomOrPolic Array.prototype.push.apply(searchTerms, chatRoomSubtitle.split(/[,\s]/)); } else { - const participantAccountIDs = report.participantAccountIDs || []; - for (let i = 0; i < participantAccountIDs.length; i++) { - const accountID = participantAccountIDs[i]; + const visibleChatMemberAccountIDs = report.visibleChatMemberAccountIDs || []; + for (let i = 0; i < visibleChatMemberAccountIDs.length; i++) { + const accountID = visibleChatMemberAccountIDs[i]; if (allPersonalDetails[accountID] && allPersonalDetails[accountID].login) { searchTerms = searchTerms.concat(allPersonalDetails[accountID].login); @@ -506,7 +506,7 @@ function createOption(accountIDs, personalDetails, report, reportActions = {}, { result.isPinned = report.isPinned; result.iouReportID = report.iouReportID; result.keyForList = String(report.reportID); - result.tooltipText = ReportUtils.getReportParticipantsTitle(report.participantAccountIDs || []); + result.tooltipText = ReportUtils.getReportParticipantsTitle(report.visibleChatMemberAccountIDs || []); result.isWaitingOnBankAccount = report.isWaitingOnBankAccount; result.policyID = report.policyID; @@ -573,7 +573,7 @@ function getPolicyExpenseReportOption(report) { const expenseReport = policyExpenseReports[`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`]; const option = createOption( - expenseReport.participantAccountIDs, + expenseReport.visibleChatMemberAccountIDs, allPersonalDetails, expenseReport, {}, @@ -1342,7 +1342,7 @@ function getOptions( const isTaskReport = ReportUtils.isTaskReport(report); const isPolicyExpenseChat = ReportUtils.isPolicyExpenseChat(report); const isMoneyRequestReport = ReportUtils.isMoneyRequestReport(report); - const accountIDs = report.participantAccountIDs || []; + const accountIDs = report.visibleChatMemberAccountIDs || []; if (isPolicyExpenseChat && report.isOwnPolicyExpenseChat && !includeOwnedWorkspaceChats) { return; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 848dfecfe4bf..1c7f859f2e12 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -211,6 +211,7 @@ type OptimisticChatReport = Pick< | 'parentReportActionID' | 'parentReportID' | 'participantAccountIDs' + | 'visibleChatMemberAccountIDs' | 'policyID' | 'reportID' | 'reportName' @@ -265,6 +266,7 @@ type OptimisticTaskReport = Pick< | 'description' | 'ownerAccountID' | 'participantAccountIDs' + | 'visibleChatMemberAccountIDs' | 'managerID' | 'type' | 'parentReportID' @@ -302,6 +304,7 @@ type OptimisticIOUReport = Pick< | 'managerID' | 'ownerAccountID' | 'participantAccountIDs' + | 'visibleChatMemberAccountIDs' | 'reportID' | 'state' | 'stateNum' @@ -2508,6 +2511,10 @@ function buildOptimisticIOUReport(payeeAccountID: number, payerAccountID: number const formattedTotal = CurrencyUtils.convertToDisplayString(total, currency); 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]; + return { type: CONST.REPORT.TYPE.IOU, cachedTotal: formattedTotal, @@ -2515,7 +2522,8 @@ function buildOptimisticIOUReport(payeeAccountID: number, payerAccountID: number currency, managerID: payerAccountID, ownerAccountID: payeeAccountID, - participantAccountIDs: [payeeAccountID, payerAccountID], + participantAccountIDs: participantsAccountIDs, + visibleChatMemberAccountIDs: participantsAccountIDs, reportID: generateReportID(), state: CONST.REPORT.STATE.SUBMITTED, stateNum: isSendingMoney ? CONST.REPORT.STATE_NUM.SUBMITTED : CONST.REPORT.STATE_NUM.PROCESSING, @@ -3051,7 +3059,9 @@ 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, policyID, reportID: generateReportID(), reportName, @@ -3253,12 +3263,16 @@ 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] : []; + return { reportID: generateReportID(), reportName: title, description, ownerAccountID, - participantAccountIDs: assigneeAccountID && assigneeAccountID !== ownerAccountID ? [assigneeAccountID] : [], + participantAccountIDs: participantsAccountIDs, + visibleChatMemberAccountIDs: participantsAccountIDs, managerID: assigneeAccountID, type: CONST.REPORT.TYPE.TASK, parentReportID, @@ -4085,6 +4099,8 @@ function getTaskAssigneeChatOnyxData( /** * Returns an array of the participants Ids of a report + * + * @deprecated Use getVisibleMemberIDs instead */ function getParticipantsIDs(report: OnyxEntry): number[] { if (!report) { @@ -4102,6 +4118,25 @@ function getParticipantsIDs(report: OnyxEntry): number[] { return participants; } +/** + * Returns an array of the visible member accountIDs for a report* + */ +function getVisibleMemberIDs(report: OnyxEntry): number[] { + if (!report) { + return []; + } + + const visibleChatMemberAccountIDs = report.visibleChatMemberAccountIDs ?? []; + + // Build participants list for IOU/expense reports + if (isMoneyRequestReport(report)) { + const onlyTruthyValues = [report.managerID, report.ownerAccountID, ...visibleChatMemberAccountIDs].filter(Boolean) as number[]; + const onlyUnique = [...new Set([...onlyTruthyValues])]; + return onlyUnique; + } + return visibleChatMemberAccountIDs; +} + /** * Return iou report action display message */ @@ -4420,6 +4455,7 @@ export { getTransactionDetails, getTaskAssigneeChatOnyxData, getParticipantsIDs, + getVisibleMemberIDs, canEditMoneyRequest, canEditFieldOfMoneyRequest, buildTransactionThread, diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 64b0f13e90b6..6e46ec320066 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -127,10 +127,10 @@ function getOrderedReportIDs( [currentReportId, allReports, betas, policies, priorityMode, allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${currentReportId}`]?.length || 1], (key, value: unknown) => { /** - * Exclude 'participantAccountIDs', 'participants' and 'lastMessageText' not to overwhelm a cached key value with huge data, + * Exclude some properties not to overwhelm a cached key value with huge data, * which we don't need to store in a cacheKey */ - if (key === 'participantAccountIDs' || key === 'participants' || key === 'lastMessageText') { + if (key === 'participantAccountIDs' || key === 'participants' || key === 'lastMessageText' || key === 'visibleChatMemberAccountIDs') { return undefined; } @@ -305,7 +305,7 @@ function getOptionData( result.isPinned = report.isPinned; result.iouReportID = report.iouReportID; result.keyForList = String(report.reportID); - result.tooltipText = ReportUtils.getReportParticipantsTitle(report.participantAccountIDs ?? []); + result.tooltipText = ReportUtils.getReportParticipantsTitle(report.visibleChatMemberAccountIDs ?? []); result.hasOutstandingChildRequest = report.hasOutstandingChildRequest; result.parentReportID = report.parentReportID ?? ''; result.isWaitingOnBankAccount = report.isWaitingOnBankAccount; diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js index f148f27b3713..212fd3ead898 100644 --- a/src/libs/actions/Policy.js +++ b/src/libs/actions/Policy.js @@ -274,11 +274,15 @@ function buildAnnounceRoomMembersOnyxData(policyID, accountIDs) { return announceRoomMembers; } + // Everyone in special policy rooms is visible + const participantAccountIDs = [...announceReport.participantAccountIDs, ...accountIDs]; + announceRoomMembers.onyxOptimisticData.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${announceReport.reportID}`, value: { - participantAccountIDs: [...announceReport.participantAccountIDs, ...accountIDs], + participantAccountIDs, + visibleChatMemberAccountIDs: participantAccountIDs, }, }); @@ -287,6 +291,7 @@ function buildAnnounceRoomMembersOnyxData(policyID, accountIDs) { key: `${ONYXKEYS.COLLECTION.REPORT}${announceReport.reportID}`, value: { participantAccountIDs: announceReport.participantAccountIDs, + visibleChatMemberAccountIDs: announceReport.visibleChatMemberAccountIDs, }, }); return announceRoomMembers; @@ -315,6 +320,7 @@ function removeOptimisticAnnounceRoomMembers(policyID, accountIDs) { key: `${ONYXKEYS.COLLECTION.REPORT}${announceReport.reportID}`, value: { participantAccountIDs: [...remainUsers], + visibleChatMemberAccountIDs: [...remainUsers], }, }); @@ -323,6 +329,7 @@ function removeOptimisticAnnounceRoomMembers(policyID, accountIDs) { key: `${ONYXKEYS.COLLECTION.REPORT}${announceReport.reportID}`, value: { participantAccountIDs: announceReport.participantAccountIDs, + visibleChatMemberAccountIDs: announceReport.visibleChatMemberAccountIDs, }, }); return announceRoomMembers; diff --git a/src/libs/actions/Report.ts b/src/libs/actions/Report.ts index 65493bc05278..043f99265b5f 100644 --- a/src/libs/actions/Report.ts +++ b/src/libs/actions/Report.ts @@ -2154,6 +2154,9 @@ function inviteToRoom(reportID: string, inviteeEmailsToAccountIDs: Record typeof accountID === 'number', ); + const visibleMemberAccountIDsAfterInvitation = [...new Set([...(report?.visibleChatMemberAccountIDs ?? []), ...inviteeAccountIDs])].filter( + (accountID): accountID is number => typeof accountID === 'number', + ); type PersonalDetailsOnyxData = { optimisticData: OnyxUpdate[]; @@ -2170,6 +2173,7 @@ function inviteToRoom(reportID: string, inviteeEmailsToAccountIDs: Record !targetAccountIDs.includes(id)); + const visibleChatMemberAccountIDsAfterRemoval = report?.visibleChatMemberAccountIDs?.filter((id: number) => !targetAccountIDs.includes(id)); const optimisticData: OnyxUpdate[] = [ { @@ -2213,6 +2219,7 @@ function removeFromRoom(reportID: string, targetAccountIDs: number[]) { key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, value: { participantAccountIDs: participantAccountIDsAfterRemoval, + visibleChatMemberAccountIDs: visibleChatMemberAccountIDsAfterRemoval, }, }, ]; @@ -2223,6 +2230,7 @@ function removeFromRoom(reportID: string, targetAccountIDs: number[]) { key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, value: { participantAccountIDs: report?.participantAccountIDs, + visibleChatMemberAccountIDs: report?.visibleChatMemberAccountIDs, }, }, ]; @@ -2235,6 +2243,7 @@ function removeFromRoom(reportID: string, targetAccountIDs: number[]) { key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, value: { participantAccountIDs: participantAccountIDsAfterRemoval, + visibleChatMemberAccountIDs: visibleChatMemberAccountIDsAfterRemoval, }, }, ]; diff --git a/src/libs/actions/Task.js b/src/libs/actions/Task.js index 1001ec4602dc..a83a2a42fa68 100644 --- a/src/libs/actions/Task.js +++ b/src/libs/actions/Task.js @@ -497,8 +497,10 @@ function editTaskAssignee(report, ownerAccountID, assigneeEmail, assigneeAccount // Check if the assignee actually changed if (assigneeAccountID && assigneeAccountID !== report.managerID && assigneeAccountID !== ownerAccountID && assigneeChatReport) { const participants = lodashGet(report, 'participantAccountIDs', []); - if (!participants.includes(assigneeAccountID)) { + const visibleMembers = lodashGet(report, 'visibleChatMemberAccountIDs', []); + if (!visibleMembers.includes(assigneeAccountID)) { optimisticReport.participantAccountIDs = [...participants, assigneeAccountID]; + optimisticReport.visibleChatMemberAccountIDs = [...visibleMembers, assigneeAccountID]; } assigneeChatReportOnyxData = ReportUtils.getTaskAssigneeChatOnyxData( diff --git a/src/pages/ReportDetailsPage.js b/src/pages/ReportDetailsPage.js index dc8b77db4ac8..ff9ed62c6a65 100644 --- a/src/pages/ReportDetailsPage.js +++ b/src/pages/ReportDetailsPage.js @@ -73,7 +73,7 @@ function ReportDetailsPage(props) { // eslint-disable-next-line react-hooks/exhaustive-deps -- policy is a dependency because `getChatRoomSubtitle` calls `getPolicyName` which in turn retrieves the value from the `policy` value stored in Onyx const chatRoomSubtitle = useMemo(() => ReportUtils.getChatRoomSubtitle(props.report), [props.report, policy]); const parentNavigationSubtitleData = ReportUtils.getParentNavigationSubtitle(props.report); - const participants = useMemo(() => ReportUtils.getParticipantsIDs(props.report), [props.report]); + const participants = useMemo(() => ReportUtils.getVisibleMemberIDs(props.report), [props.report]); const isGroupDMChat = useMemo(() => ReportUtils.isDM(props.report) && participants.length > 1, [props.report, participants.length]); diff --git a/src/pages/ReportParticipantsPage.js b/src/pages/ReportParticipantsPage.js index fddf5176f815..7dbc1c7036c4 100755 --- a/src/pages/ReportParticipantsPage.js +++ b/src/pages/ReportParticipantsPage.js @@ -56,7 +56,7 @@ const defaultProps = { * @return {Array} */ const getAllParticipants = (report, personalDetails, translate) => - _.chain(ReportUtils.getParticipantsIDs(report)) + _.chain(ReportUtils.getVisibleMemberIDs(report)) .map((accountID, index) => { const userPersonalDetail = lodashGet(personalDetails, accountID, {displayName: personalDetails.displayName || translate('common.hidden'), avatar: ''}); const userLogin = LocalePhoneNumber.formatPhoneNumber(userPersonalDetail.login || '') || translate('common.hidden'); diff --git a/src/pages/RoomInvitePage.js b/src/pages/RoomInvitePage.js index aebdec047895..f8d2d32bfe79 100644 --- a/src/pages/RoomInvitePage.js +++ b/src/pages/RoomInvitePage.js @@ -70,7 +70,10 @@ function RoomInvitePage(props) { const [userToInvite, setUserToInvite] = useState(null); // Any existing participants and Expensify emails should not be eligible for invitation - const excludedUsers = useMemo(() => [...PersonalDetailsUtils.getLoginsByAccountIDs(lodashGet(props.report, 'participantAccountIDs', [])), ...CONST.EXPENSIFY_EMAILS], [props.report]); + const excludedUsers = useMemo( + () => [...PersonalDetailsUtils.getLoginsByAccountIDs(lodashGet(props.report, 'visibleChatMemberAccountIDs', [])), ...CONST.EXPENSIFY_EMAILS], + [props.report], + ); useEffect(() => { const inviteOptions = OptionsListUtils.getMemberInviteOptions(props.personalDetails, props.betas, searchTerm, excludedUsers); diff --git a/src/pages/RoomMembersPage.js b/src/pages/RoomMembersPage.js index 27e1cd1da2e6..67228e574de6 100644 --- a/src/pages/RoomMembersPage.js +++ b/src/pages/RoomMembersPage.js @@ -174,7 +174,7 @@ function RoomMembersPage(props) { const getMemberOptions = () => { let result = []; - _.each(props.report.participantAccountIDs, (accountID) => { + _.each(props.report.visibleChatMemberAccountIDs, (accountID) => { const details = personalDetails[accountID]; if (!details) { diff --git a/src/pages/ShareCodePage.js b/src/pages/ShareCodePage.js index 1f062a42f8bf..344e7f4bc886 100644 --- a/src/pages/ShareCodePage.js +++ b/src/pages/ShareCodePage.js @@ -55,7 +55,7 @@ class ShareCodePage extends React.Component { } if (ReportUtils.isMoneyRequestReport(this.props.report)) { // generate subtitle from participants - return _.map(ReportUtils.getParticipantsIDs(this.props.report), (accountID) => ReportUtils.getDisplayNameForParticipant(accountID)).join(' & '); + return _.map(ReportUtils.getVisibleMemberIDs(this.props.report), (accountID) => ReportUtils.getDisplayNameForParticipant(accountID)).join(' & '); } if (isReport) { diff --git a/src/pages/reportPropTypes.js b/src/pages/reportPropTypes.js index d2d7bb480ed6..329bf66f7275 100644 --- a/src/pages/reportPropTypes.js +++ b/src/pages/reportPropTypes.js @@ -47,6 +47,9 @@ export default PropTypes.shape({ /** List of accountIDs of participants of the report */ participantAccountIDs: PropTypes.arrayOf(PropTypes.number), + /** List of accountIDs of visible members of the report */ + visibleChatMemberAccountIDs: PropTypes.arrayOf(PropTypes.number), + /** Linked policy's ID */ policyID: PropTypes.string, diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts index 840bbd1e2e2f..f6af87038d00 100644 --- a/src/types/onyx/Report.ts +++ b/src/types/onyx/Report.ts @@ -120,6 +120,7 @@ type Report = { lastActorAccountID?: number; ownerAccountID?: number; participantAccountIDs?: number[]; + visibleChatMemberAccountIDs?: number[]; total?: number; currency?: string; parentReportActionIDs?: number[]; diff --git a/tests/unit/OptionsListUtilsTest.js b/tests/unit/OptionsListUtilsTest.js index 9ac0186d2396..961df1fa3e90 100644 --- a/tests/unit/OptionsListUtilsTest.js +++ b/tests/unit/OptionsListUtilsTest.js @@ -16,6 +16,7 @@ describe('OptionsListUtils', () => { isPinned: false, reportID: 1, participantAccountIDs: [2, 1], + visibleChatMemberAccountIDs: [2, 1], reportName: 'Iron Man, Mister Fantastic', hasDraft: true, type: CONST.REPORT.TYPE.CHAT, @@ -26,6 +27,7 @@ describe('OptionsListUtils', () => { isPinned: false, reportID: 2, participantAccountIDs: [3], + visibleChatMemberAccountIDs: [3], reportName: 'Spider-Man', type: CONST.REPORT.TYPE.CHAT, }, @@ -37,6 +39,7 @@ describe('OptionsListUtils', () => { isPinned: true, reportID: 3, participantAccountIDs: [1], + visibleChatMemberAccountIDs: [1], reportName: 'Mister Fantastic', type: CONST.REPORT.TYPE.CHAT, }, @@ -46,6 +49,7 @@ describe('OptionsListUtils', () => { isPinned: false, reportID: 4, participantAccountIDs: [4], + visibleChatMemberAccountIDs: [4], reportName: 'Black Panther', type: CONST.REPORT.TYPE.CHAT, }, @@ -55,6 +59,7 @@ describe('OptionsListUtils', () => { isPinned: false, reportID: 5, participantAccountIDs: [5], + visibleChatMemberAccountIDs: [5], reportName: 'Invisible Woman', type: CONST.REPORT.TYPE.CHAT, }, @@ -64,6 +69,7 @@ describe('OptionsListUtils', () => { isPinned: false, reportID: 6, participantAccountIDs: [6], + visibleChatMemberAccountIDs: [6], reportName: 'Thor', type: CONST.REPORT.TYPE.CHAT, }, @@ -75,6 +81,7 @@ describe('OptionsListUtils', () => { isPinned: false, reportID: 7, participantAccountIDs: [7], + visibleChatMemberAccountIDs: [7], reportName: 'Captain America', type: CONST.REPORT.TYPE.CHAT, }, @@ -86,6 +93,7 @@ describe('OptionsListUtils', () => { isPinned: false, reportID: 8, participantAccountIDs: [12], + visibleChatMemberAccountIDs: [12], reportName: 'Silver Surfer', type: CONST.REPORT.TYPE.CHAT, }, @@ -97,6 +105,7 @@ describe('OptionsListUtils', () => { isPinned: false, reportID: 9, participantAccountIDs: [8], + visibleChatMemberAccountIDs: [8], reportName: 'Mister Sinister', iouReportID: 100, type: CONST.REPORT.TYPE.CHAT, @@ -109,6 +118,7 @@ describe('OptionsListUtils', () => { reportID: 10, isPinned: false, participantAccountIDs: [2, 7], + visibleChatMemberAccountIDs: [2, 7], reportName: '', oldPolicyName: "SHIELD's workspace", chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, @@ -187,6 +197,7 @@ describe('OptionsListUtils', () => { isPinned: false, reportID: 11, participantAccountIDs: [999], + visibleChatMemberAccountIDs: [999], reportName: 'Concierge', type: CONST.REPORT.TYPE.CHAT, }, @@ -200,6 +211,7 @@ describe('OptionsListUtils', () => { isPinned: false, reportID: 12, participantAccountIDs: [1000], + visibleChatMemberAccountIDs: [1000], reportName: 'Chronos', type: CONST.REPORT.TYPE.CHAT, }, @@ -213,6 +225,7 @@ describe('OptionsListUtils', () => { isPinned: false, reportID: 13, participantAccountIDs: [1001], + visibleChatMemberAccountIDs: [1001], reportName: 'Receipts', type: CONST.REPORT.TYPE.CHAT, }, @@ -226,6 +239,7 @@ describe('OptionsListUtils', () => { isPinned: false, reportID: 14, participantAccountIDs: [1, 10, 3], + visibleChatMemberAccountIDs: [1, 10, 3], reportName: '', oldPolicyName: 'Avengers Room', isArchivedRoom: false,