From 68bc20be92737b57e045b5a00491eccd0d589e33 Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Tue, 19 Dec 2023 16:34:51 +0100 Subject: [PATCH 01/14] Add policyID to getOrderedReportIDs and sortReportsByLastRead --- src/libs/ReportUtils.ts | 11 ++++++++--- src/libs/SidebarUtils.ts | 11 +++++++++-- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index fce158c309b8..3f29607dee39 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -568,10 +568,15 @@ function isDraftExpenseReport(report: OnyxEntry): boolean { } /** - * Given a collection of reports returns them sorted by last read + * Given a collection of reports returns them sorted by last read and optionally filtered by a specific policyID. */ -function sortReportsByLastRead(reports: OnyxCollection): Array> { - return Object.values(reports ?? {}) +function sortReportsByLastRead(reports: OnyxCollection, policyID?: string): Array> { + let reportsValues = Object.values(reports ?? {}); + if (policyID) { + reportsValues = reportsValues.filter((report) => report?.policyID === policyID); + } + + return reportsValues .filter((report) => !!report?.reportID && !!report?.lastReadTime) .sort((a, b) => { const aTime = new Date(a?.lastReadTime ?? ''); diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 4744426ecfd3..8d18041a1cdd 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -118,11 +118,12 @@ function getOrderedReportIDs( policies: Record, priorityMode: ValueOf, allReportActions: OnyxCollection, + policyID = '', ): string[] { // Generate a unique cache key based on the function arguments const cachedReportsKey = JSON.stringify( // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - [currentReportId, allReports, betas, policies, priorityMode, allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${currentReportId}`]?.length || 1], + [policyID, 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, @@ -173,8 +174,14 @@ function getOrderedReportIDs( const nonArchivedReports: Report[] = []; const archivedReports: Report[] = []; + let workspaceReportsToDisplay: Report[] = reportsToDisplay; + + if (policyID) { + workspaceReportsToDisplay = workspaceReportsToDisplay.filter((report) => report.policyID === policyID); + } + // There are a few properties that need to be calculated for the report which are used when sorting reports. - reportsToDisplay.forEach((report) => { + workspaceReportsToDisplay.forEach((report) => { // Normally, the spread operator would be used here to clone the report and prevent the need to reassign the params. // However, this code needs to be very performant to handle thousands of reports, so in the interest of speed, we're just going to disable this lint rule and add // the reportDisplayName property to the report object directly. From 3519aa3c8c28f3916e87468a4459a58623f88202 Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Tue, 19 Dec 2023 17:39:55 +0100 Subject: [PATCH 02/14] Refactor getOrderedReportIDs function --- src/libs/SidebarUtils.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 8d18041a1cdd..008f528d64d7 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -118,12 +118,12 @@ function getOrderedReportIDs( policies: Record, priorityMode: ValueOf, allReportActions: OnyxCollection, - policyID = '', + currentPolicyID = '', ): string[] { // Generate a unique cache key based on the function arguments const cachedReportsKey = JSON.stringify( // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - [policyID, currentReportId, allReports, betas, policies, priorityMode, allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${currentReportId}`]?.length || 1], + [currentPolicyID, 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, @@ -176,8 +176,8 @@ function getOrderedReportIDs( let workspaceReportsToDisplay: Report[] = reportsToDisplay; - if (policyID) { - workspaceReportsToDisplay = workspaceReportsToDisplay.filter((report) => report.policyID === policyID); + if (currentPolicyID) { + workspaceReportsToDisplay = workspaceReportsToDisplay.filter((report) => report.policyID === currentPolicyID); } // There are a few properties that need to be calculated for the report which are used when sorting reports. From fd51ac7048a25f301d44fad1adbd3ec9edb65415 Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Wed, 20 Dec 2023 15:35:57 +0100 Subject: [PATCH 03/14] Refactor getOrderedReportIDs function --- src/libs/SidebarUtils.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 008f528d64d7..fc6b6f7d8eeb 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -150,7 +150,7 @@ function getOrderedReportIDs( const isInDefaultMode = !isInGSDMode; const allReportsDictValues = Object.values(allReports); // Filter out all the reports that shouldn't be displayed - const reportsToDisplay = allReportsDictValues.filter((report) => ReportUtils.shouldReportBeInOptionList(report, currentReportId ?? '', isInGSDMode, betas, policies, true)); + let reportsToDisplay = allReportsDictValues.filter((report) => ReportUtils.shouldReportBeInOptionList(report, currentReportId ?? '', isInGSDMode, betas, policies, true)); if (reportsToDisplay.length === 0) { // Display Concierge chat report when there is no report to be displayed @@ -174,14 +174,12 @@ function getOrderedReportIDs( const nonArchivedReports: Report[] = []; const archivedReports: Report[] = []; - let workspaceReportsToDisplay: Report[] = reportsToDisplay; - if (currentPolicyID) { - workspaceReportsToDisplay = workspaceReportsToDisplay.filter((report) => report.policyID === currentPolicyID); + reportsToDisplay = reportsToDisplay.filter((report) => report.policyID === currentPolicyID); } // There are a few properties that need to be calculated for the report which are used when sorting reports. - workspaceReportsToDisplay.forEach((report) => { + reportsToDisplay.forEach((report) => { // Normally, the spread operator would be used here to clone the report and prevent the need to reassign the params. // However, this code needs to be very performant to handle thousands of reports, so in the interest of speed, we're just going to disable this lint rule and add // the reportDisplayName property to the report object directly. From baf40b727e8f605d7c1c86f3d7844d88f6c838bc Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Tue, 16 Jan 2024 10:19:56 +0100 Subject: [PATCH 04/14] Add openWorkspace request --- src/libs/SidebarUtils.ts | 20 +++++++++++++++++--- src/libs/actions/Policy.js | 13 +++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 76845a1e070a..87d68023039a 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -80,6 +80,18 @@ function isSidebarLoadedReady(): Promise { return sidebarIsReadyPromise; } +function hasReportCommonPolicyMember(reportParticipantAccountIDs: number[], policyMembersAccountIDs: string[]) { + const set1 = new Set(policyMembersAccountIDs); + + for (const reportParticipant of reportParticipantAccountIDs) { + if (set1.has(reportParticipant.toString())) { + return true; + } + } + + return false; +} + function compareStringDates(a: string, b: string): 0 | 1 | -1 { if (a < b) { return -1; @@ -121,6 +133,7 @@ function getOrderedReportIDs( priorityMode: ValueOf, allReportActions: OnyxCollection, currentPolicyID = '', + policyMembersAccountIDs: string[] = [], ): string[] { // Generate a unique cache key based on the function arguments const cachedReportsKey = JSON.stringify( @@ -176,10 +189,11 @@ function getOrderedReportIDs( const nonArchivedReports: Report[] = []; const archivedReports: Report[] = []; - if (currentPolicyID) { - reportsToDisplay = reportsToDisplay.filter((report) => report.policyID === currentPolicyID); + if (currentPolicyID || policyMembersAccountIDs.length > 0) { + reportsToDisplay = reportsToDisplay.filter((report) => + report.policyID === '_FAKE_' ? hasReportCommonPolicyMember(report.participantAccountIDs ?? [], policyMembersAccountIDs) : report.policyID === currentPolicyID, + ); } - // There are a few properties that need to be calculated for the report which are used when sorting reports. reportsToDisplay.forEach((report) => { // Normally, the spread operator would be used here to clone the report and prevent the need to reassign the params. diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js index a21b795fa89a..2ecd64cb7478 100644 --- a/src/libs/actions/Policy.js +++ b/src/libs/actions/Policy.js @@ -1423,6 +1423,18 @@ function openWorkspaceReimburseView(policyID) { API.read('OpenWorkspaceReimburseView', {policyID}, onyxData); } +function openWorkspace(policyID, clientMemberAccountIDs) { + if (!policyID || !clientMemberAccountIDs) { + Log.warn('openWorkspace invalid params', {policyID, clientMemberAccountIDs}); + return; + } + + API.read('OpenWorkspace', { + policyID, + clientMemberAccountIDs: JSON.stringify(clientMemberAccountIDs), + }); +} + function openWorkspaceMembersPage(policyID, clientMemberEmails) { if (!policyID || !clientMemberEmails) { Log.warn('openWorkspaceMembersPage invalid params', {policyID, clientMemberEmails}); @@ -1945,6 +1957,7 @@ export { createWorkspace, openWorkspaceMembersPage, openWorkspaceInvitePage, + openWorkspace, removeWorkspace, createWorkspaceFromIOUPayment, setWorkspaceInviteMembersDraft, From 2788c7419b1eab16b9c37f55362d942e0b26242b Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Tue, 16 Jan 2024 17:18:02 +0100 Subject: [PATCH 05/14] Add policyID and policyMemberAccountIDs params to findLastAccessedReport --- .../AppNavigator/ReportScreenIDSetter.ts | 13 +++++- src/libs/ReportUtils.ts | 40 +++++++++++++++++-- src/libs/SidebarUtils.ts | 16 +------- 3 files changed, 50 insertions(+), 19 deletions(-) diff --git a/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts b/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts index e6e9a7746585..ee936f887ff1 100644 --- a/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts +++ b/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts @@ -34,8 +34,19 @@ const getLastAccessedReportID = ( isFirstTimeNewExpensifyUser: OnyxEntry, openOnAdminRoom: boolean, reportMetadata: OnyxCollection, + policyID?: string, + policyMemberAccountIDs?: string[], ): string | undefined => { - const lastReport = ReportUtils.findLastAccessedReport(reports, ignoreDefaultRooms, policies, !!isFirstTimeNewExpensifyUser, openOnAdminRoom, reportMetadata); + const lastReport = ReportUtils.findLastAccessedReport( + reports, + ignoreDefaultRooms, + policies, + !!isFirstTimeNewExpensifyUser, + openOnAdminRoom, + reportMetadata, + policyID, + policyMemberAccountIDs, + ); return lastReport?.reportID; }; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 1c1d5832a6bb..6d0b06c266f0 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -615,13 +615,44 @@ function isDraftExpenseReport(report: OnyxEntry | EmptyObject): boolean return isExpenseReport(report) && report?.stateNum === CONST.REPORT.STATE_NUM.OPEN && report?.statusNum === CONST.REPORT.STATUS.OPEN; } +/** + * Checks if the supplied report has a common policy member with the array passed in params. + */ +function hasReportCommonPolicyMemberWithArray(report: Report, policyMembersAccountIDs: string[]) { + if (!report.participantAccountIDs) { + return false; + } + + const policyMembersAccountIDsSet = new Set(policyMembersAccountIDs); + + for (const reportParticipant of report.participantAccountIDs) { + if (policyMembersAccountIDsSet.has(reportParticipant.toString())) { + return true; + } + } + + return false; +} + +/** + * Checks if the supplied report belongs to workspace based on the provided params. + */ +function doesReportBelongToWorkspace(report: Report, policyID: string, policyMembersAccountIDs: string[]) { + return report.policyID === '_FAKE_' ? hasReportCommonPolicyMemberWithArray(report, policyMembersAccountIDs) : report.policyID === policyID; +} + /** * Given a collection of reports, return them sorted by the last read timestamp. Filters the sorted reports by a policy ID, if provided. */ -function sortReportsByLastRead(reports: OnyxCollection, reportMetadata: OnyxCollection, policyID?: string): Array> { +function sortReportsByLastRead( + reports: OnyxCollection, + reportMetadata: OnyxCollection, + policyID?: string, + policyMemberAccountIDs: string[] = [], +): Array> { let reportsValues = Object.values(reports ?? {}); if (policyID) { - reportsValues = reportsValues.filter((report) => report?.policyID === policyID); + reportsValues = reportsValues.filter((report) => !!report && doesReportBelongToWorkspace(report, policyID, policyMemberAccountIDs)); } return reportsValues @@ -901,13 +932,15 @@ function findLastAccessedReport( isFirstTimeNewExpensifyUser: boolean, openOnAdminRoom = false, reportMetadata: OnyxCollection = {}, + policyID?: string, + policyMemberAccountIDs?: string[], ): OnyxEntry { // If it's the user's first time using New Expensify, then they could either have: // - just a Concierge report, if so we'll return that // - their Concierge report, and a separate report that must have deeplinked them to the app before they created their account. // If it's the latter, we'll use the deeplinked report over the Concierge report, // since the Concierge report would be incorrectly selected over the deep-linked report in the logic below. - let sortedReports = sortReportsByLastRead(reports, reportMetadata); + let sortedReports = sortReportsByLastRead(reports, reportMetadata, policyID, policyMemberAccountIDs); let adminReport: OnyxEntry | undefined; if (openOnAdminRoom) { @@ -4538,6 +4571,7 @@ export { shouldAutoFocusOnKeyPress, shouldDisplayThreadReplies, shouldDisableThread, + doesReportBelongToWorkspace, }; export type {ExpenseOriginalMessage, OptionData, OptimisticChatReport, OptimisticCreatedReportAction}; diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 87d68023039a..9d2e06fc8552 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -80,18 +80,6 @@ function isSidebarLoadedReady(): Promise { return sidebarIsReadyPromise; } -function hasReportCommonPolicyMember(reportParticipantAccountIDs: number[], policyMembersAccountIDs: string[]) { - const set1 = new Set(policyMembersAccountIDs); - - for (const reportParticipant of reportParticipantAccountIDs) { - if (set1.has(reportParticipant.toString())) { - return true; - } - } - - return false; -} - function compareStringDates(a: string, b: string): 0 | 1 | -1 { if (a < b) { return -1; @@ -190,9 +178,7 @@ function getOrderedReportIDs( const archivedReports: Report[] = []; if (currentPolicyID || policyMembersAccountIDs.length > 0) { - reportsToDisplay = reportsToDisplay.filter((report) => - report.policyID === '_FAKE_' ? hasReportCommonPolicyMember(report.participantAccountIDs ?? [], policyMembersAccountIDs) : report.policyID === currentPolicyID, - ); + reportsToDisplay = reportsToDisplay.filter((report) => ReportUtils.doesReportBelongToWorkspace(report, currentPolicyID, policyMembersAccountIDs)); } // There are a few properties that need to be calculated for the report which are used when sorting reports. reportsToDisplay.forEach((report) => { From 269cde17afeabf92340a65a2a3f2b9f020debb31 Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Wed, 17 Jan 2024 07:41:29 +0100 Subject: [PATCH 06/14] Refactor doesReportBelongToWorkspace function --- src/CONST.ts | 1 + src/libs/ReportUtils.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/CONST.ts b/src/CONST.ts index d6f3d3cdcef6..3ee39d30267f 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -1296,6 +1296,7 @@ const CONST = { CUSTOM_UNIT_RATE_BASE_OFFSET: 100, OWNER_EMAIL_FAKE: '_FAKE_', OWNER_ACCOUNT_ID_FAKE: 0, + ID_FAKE: '_FAKE_', }, CUSTOM_UNITS: { diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 33bd3b376956..717ec7f44f1e 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -643,7 +643,7 @@ function hasReportCommonPolicyMemberWithArray(report: Report, policyMembersAccou * Checks if the supplied report belongs to workspace based on the provided params. */ function doesReportBelongToWorkspace(report: Report, policyID: string, policyMembersAccountIDs: string[]) { - return report.policyID === '_FAKE_' ? hasReportCommonPolicyMemberWithArray(report, policyMembersAccountIDs) : report.policyID === policyID; + return report.policyID === CONST.POLICY.ID_FAKE || !report.policyID ? hasReportCommonPolicyMemberWithArray(report, policyMembersAccountIDs) : report.policyID === policyID; } /** From a92114a6ae9e9b611789696df6f4b2bf364899c1 Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Wed, 17 Jan 2024 08:18:41 +0100 Subject: [PATCH 07/14] Refactor getOrderedReportIDs function --- src/libs/SidebarUtils.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 9d2e06fc8552..037cca24ed11 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -126,7 +126,16 @@ function getOrderedReportIDs( // Generate a unique cache key based on the function arguments const cachedReportsKey = JSON.stringify( // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - [currentPolicyID, currentReportId, allReports, betas, policies, priorityMode, allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${currentReportId}`]?.length || 1], + [ + currentReportId, + allReports, + betas, + policies, + priorityMode, + allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${currentReportId}`]?.length ?? 1, + currentPolicyID, + policyMembersAccountIDs, + ], (key, value: unknown) => { /** * Exclude some properties not to overwhelm a cached key value with huge data, From 03fcbd8cceb062f739eb8c2186a2e3958d176d77 Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Wed, 17 Jan 2024 09:17:40 +0100 Subject: [PATCH 08/14] Update sortReportsByLastRead description --- src/libs/ReportUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 717ec7f44f1e..2d02d511b9bf 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -647,7 +647,7 @@ function doesReportBelongToWorkspace(report: Report, policyID: string, policyMem } /** - * Given a collection of reports, return them sorted by the last read timestamp. Filters the sorted reports by a policy ID, if provided. + * Given a collection of reports, return them sorted by the last read timestamp. Filters the sorted reports by a policy ID and policyMemberAccountIDs, if provided. */ function sortReportsByLastRead( reports: OnyxCollection, From 8f398a61522a2235f99cd47d05513b71e24a7187 Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Wed, 17 Jan 2024 18:20:55 +0100 Subject: [PATCH 09/14] Additional check for Concierge in doesReportBelongToWorkspace --- src/libs/ReportUtils.ts | 65 +++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 31 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 2d02d511b9bf..a2562f34bc43 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -639,37 +639,6 @@ function hasReportCommonPolicyMemberWithArray(report: Report, policyMembersAccou return false; } -/** - * Checks if the supplied report belongs to workspace based on the provided params. - */ -function doesReportBelongToWorkspace(report: Report, policyID: string, policyMembersAccountIDs: string[]) { - return report.policyID === CONST.POLICY.ID_FAKE || !report.policyID ? hasReportCommonPolicyMemberWithArray(report, policyMembersAccountIDs) : report.policyID === policyID; -} - -/** - * Given a collection of reports, return them sorted by the last read timestamp. Filters the sorted reports by a policy ID and policyMemberAccountIDs, if provided. - */ -function sortReportsByLastRead( - reports: OnyxCollection, - reportMetadata: OnyxCollection, - policyID?: string, - policyMemberAccountIDs: string[] = [], -): Array> { - let reportsValues = Object.values(reports ?? {}); - if (policyID) { - reportsValues = reportsValues.filter((report) => !!report && doesReportBelongToWorkspace(report, policyID, policyMemberAccountIDs)); - } - - return reportsValues - .filter((report) => !!report?.reportID && !!(reportMetadata?.[`${ONYXKEYS.COLLECTION.REPORT_METADATA}${report.reportID}`]?.lastVisitTime ?? report?.lastReadTime)) - .sort((a, b) => { - const aTime = new Date(reportMetadata?.[`${ONYXKEYS.COLLECTION.REPORT_METADATA}${a?.reportID}`]?.lastVisitTime ?? a?.lastReadTime ?? ''); - const bTime = new Date(reportMetadata?.[`${ONYXKEYS.COLLECTION.REPORT_METADATA}${b?.reportID}`]?.lastVisitTime ?? b?.lastReadTime ?? ''); - - return aTime.valueOf() - bTime.valueOf(); - }); -} - /** * Whether the Money Request report is settled */ @@ -867,6 +836,40 @@ function isConciergeChatReport(report: OnyxEntry): boolean { return report?.participantAccountIDs?.length === 1 && Number(report.participantAccountIDs?.[0]) === CONST.ACCOUNT_ID.CONCIERGE && !isChatThread(report); } +/** + * Checks if the supplied report belongs to workspace based on the provided params. + */ +function doesReportBelongToWorkspace(report: Report, policyID: string, policyMembersAccountIDs: string[]) { + return ( + isConciergeChatReport(report) || + (report.policyID === CONST.POLICY.ID_FAKE || !report.policyID ? hasReportCommonPolicyMemberWithArray(report, policyMembersAccountIDs) : report.policyID === policyID) + ); +} + +/** + * Given a collection of reports, return them sorted by the last read timestamp. Filters the sorted reports by a policy ID and policyMemberAccountIDs, if provided. + */ +function sortReportsByLastRead( + reports: OnyxCollection, + reportMetadata: OnyxCollection, + policyID?: string, + policyMemberAccountIDs: string[] = [], +): Array> { + let reportsValues = Object.values(reports ?? {}); + if (policyID) { + reportsValues = reportsValues.filter((report) => !!report && doesReportBelongToWorkspace(report, policyID, policyMemberAccountIDs)); + } + + return reportsValues + .filter((report) => !!report?.reportID && !!(reportMetadata?.[`${ONYXKEYS.COLLECTION.REPORT_METADATA}${report.reportID}`]?.lastVisitTime ?? report?.lastReadTime)) + .sort((a, b) => { + const aTime = new Date(reportMetadata?.[`${ONYXKEYS.COLLECTION.REPORT_METADATA}${a?.reportID}`]?.lastVisitTime ?? a?.lastReadTime ?? ''); + const bTime = new Date(reportMetadata?.[`${ONYXKEYS.COLLECTION.REPORT_METADATA}${b?.reportID}`]?.lastVisitTime ?? b?.lastReadTime ?? ''); + + return aTime.valueOf() - bTime.valueOf(); + }); +} + /** * Returns true if report is still being processed */ From 94b6a69fefa8e9397fb50d67fa1dee15fff9027d Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Wed, 17 Jan 2024 19:02:26 +0100 Subject: [PATCH 10/14] Fix policyMemberAccountIDs types --- .../AppNavigator/ReportScreenIDSetter.ts | 2 +- src/libs/ReportUtils.ts | 14 +++++++------- src/libs/SidebarUtils.ts | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts b/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts index ee936f887ff1..0cc6aa27e313 100644 --- a/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts +++ b/src/libs/Navigation/AppNavigator/ReportScreenIDSetter.ts @@ -35,7 +35,7 @@ const getLastAccessedReportID = ( openOnAdminRoom: boolean, reportMetadata: OnyxCollection, policyID?: string, - policyMemberAccountIDs?: string[], + policyMemberAccountIDs?: number[], ): string | undefined => { const lastReport = ReportUtils.findLastAccessedReport( reports, diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index a2562f34bc43..b527df55bffc 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -623,15 +623,15 @@ function isDraftExpenseReport(report: OnyxEntry | EmptyObject): boolean /** * Checks if the supplied report has a common policy member with the array passed in params. */ -function hasReportCommonPolicyMemberWithArray(report: Report, policyMembersAccountIDs: string[]) { +function hasReportCommonPolicyMemberWithArray(report: Report, policyMemberAccountIDs: number[]) { if (!report.participantAccountIDs) { return false; } - const policyMembersAccountIDsSet = new Set(policyMembersAccountIDs); + const policyMemberAccountIDsSet = new Set(policyMemberAccountIDs); for (const reportParticipant of report.participantAccountIDs) { - if (policyMembersAccountIDsSet.has(reportParticipant.toString())) { + if (policyMemberAccountIDsSet.has(reportParticipant)) { return true; } } @@ -839,10 +839,10 @@ function isConciergeChatReport(report: OnyxEntry): boolean { /** * Checks if the supplied report belongs to workspace based on the provided params. */ -function doesReportBelongToWorkspace(report: Report, policyID: string, policyMembersAccountIDs: string[]) { +function doesReportBelongToWorkspace(report: Report, policyID: string, policyMemberAccountIDs: number[]) { return ( isConciergeChatReport(report) || - (report.policyID === CONST.POLICY.ID_FAKE || !report.policyID ? hasReportCommonPolicyMemberWithArray(report, policyMembersAccountIDs) : report.policyID === policyID) + (report.policyID === CONST.POLICY.ID_FAKE || !report.policyID ? hasReportCommonPolicyMemberWithArray(report, policyMemberAccountIDs) : report.policyID === policyID) ); } @@ -853,7 +853,7 @@ function sortReportsByLastRead( reports: OnyxCollection, reportMetadata: OnyxCollection, policyID?: string, - policyMemberAccountIDs: string[] = [], + policyMemberAccountIDs: number[] = [], ): Array> { let reportsValues = Object.values(reports ?? {}); if (policyID) { @@ -941,7 +941,7 @@ function findLastAccessedReport( openOnAdminRoom = false, reportMetadata: OnyxCollection = {}, policyID?: string, - policyMemberAccountIDs?: string[], + policyMemberAccountIDs?: number[], ): OnyxEntry { // If it's the user's first time using New Expensify, then they could either have: // - just a Concierge report, if so we'll return that diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 037cca24ed11..332ee61a29d8 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -121,7 +121,7 @@ function getOrderedReportIDs( priorityMode: ValueOf, allReportActions: OnyxCollection, currentPolicyID = '', - policyMembersAccountIDs: string[] = [], + policyMemberAccountIDs: number[] = [], ): string[] { // Generate a unique cache key based on the function arguments const cachedReportsKey = JSON.stringify( @@ -134,7 +134,7 @@ function getOrderedReportIDs( priorityMode, allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${currentReportId}`]?.length ?? 1, currentPolicyID, - policyMembersAccountIDs, + policyMemberAccountIDs, ], (key, value: unknown) => { /** @@ -186,8 +186,8 @@ function getOrderedReportIDs( const nonArchivedReports: Report[] = []; const archivedReports: Report[] = []; - if (currentPolicyID || policyMembersAccountIDs.length > 0) { - reportsToDisplay = reportsToDisplay.filter((report) => ReportUtils.doesReportBelongToWorkspace(report, currentPolicyID, policyMembersAccountIDs)); + if (currentPolicyID || policyMemberAccountIDs.length > 0) { + reportsToDisplay = reportsToDisplay.filter((report) => ReportUtils.doesReportBelongToWorkspace(report, currentPolicyID, policyMemberAccountIDs)); } // There are a few properties that need to be calculated for the report which are used when sorting reports. reportsToDisplay.forEach((report) => { From af744fdfa2e4c00e319d5eead7774d23734c3143 Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Thu, 18 Jan 2024 08:50:01 +0100 Subject: [PATCH 11/14] Refactor function names and descriptions --- src/libs/ReportUtils.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index b527df55bffc..207e2071a877 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -623,7 +623,7 @@ function isDraftExpenseReport(report: OnyxEntry | EmptyObject): boolean /** * Checks if the supplied report has a common policy member with the array passed in params. */ -function hasReportCommonPolicyMemberWithArray(report: Report, policyMemberAccountIDs: number[]) { +function hasParticipantInArray(report: Report, policyMemberAccountIDs: number[]) { if (!report.participantAccountIDs) { return false; } @@ -837,12 +837,12 @@ function isConciergeChatReport(report: OnyxEntry): boolean { } /** - * Checks if the supplied report belongs to workspace based on the provided params. + * Checks if the supplied report belongs to workspace based on the provided params. If the report's policyID is _FAKE_ or has no value, it means this report is a DM. + * In this case report and workspace members must be compared to deteremine whether the report belongs to the workspace. */ function doesReportBelongToWorkspace(report: Report, policyID: string, policyMemberAccountIDs: number[]) { return ( - isConciergeChatReport(report) || - (report.policyID === CONST.POLICY.ID_FAKE || !report.policyID ? hasReportCommonPolicyMemberWithArray(report, policyMemberAccountIDs) : report.policyID === policyID) + isConciergeChatReport(report) || (report.policyID === CONST.POLICY.ID_FAKE || !report.policyID ? hasParticipantInArray(report, policyMemberAccountIDs) : report.policyID === policyID) ); } From 57bda5c87d25eeed7fbd44a896e8765bd797d945 Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Thu, 18 Jan 2024 09:59:34 +0100 Subject: [PATCH 12/14] Add filterReportsByPolicyIdAndMemberAccountIDs function --- src/libs/ReportUtils.ts | 32 ++++++++++++++++++-------------- tests/unit/ReportUtilsTest.js | 18 +++++++++--------- 2 files changed, 27 insertions(+), 23 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 9745e1a38f4d..062df807c61c 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -842,20 +842,17 @@ function doesReportBelongToWorkspace(report: Report, policyID: string, policyMem } /** - * Given a collection of reports, return them sorted by the last read timestamp. Filters the sorted reports by a policy ID and policyMemberAccountIDs, if provided. + * Given a collection of reports, return them filtered by a policyID and policyMemberAccountIDs. */ -function sortReportsByLastRead( - reports: OnyxCollection, - reportMetadata: OnyxCollection, - policyID?: string, - policyMemberAccountIDs: number[] = [], -): Array> { - let reportsValues = Object.values(reports ?? {}); - if (policyID) { - reportsValues = reportsValues.filter((report) => !!report && doesReportBelongToWorkspace(report, policyID, policyMemberAccountIDs)); - } +function filterReportsByPolicyIdAndMemberAccountIDs(reports: Report[], policyID = '', policyMemberAccountIDs: number[] = []) { + return reports.filter((report) => !!report && doesReportBelongToWorkspace(report, policyID, policyMemberAccountIDs)); +} - return reportsValues +/** + * Given a collection of reports, return them sorted by the last read timestamp. + */ +function sortReportsByLastRead(reports: Report[], reportMetadata: OnyxCollection): Array> { + return reports .filter((report) => !!report?.reportID && !!(reportMetadata?.[`${ONYXKEYS.COLLECTION.REPORT_METADATA}${report.reportID}`]?.lastVisitTime ?? report?.lastReadTime)) .sort((a, b) => { const aTime = new Date(reportMetadata?.[`${ONYXKEYS.COLLECTION.REPORT_METADATA}${a?.reportID}`]?.lastVisitTime ?? a?.lastReadTime ?? ''); @@ -936,14 +933,21 @@ function findLastAccessedReport( openOnAdminRoom = false, reportMetadata: OnyxCollection = {}, policyID?: string, - policyMemberAccountIDs?: number[], + policyMemberAccountIDs: number[] = [], ): OnyxEntry { // If it's the user's first time using New Expensify, then they could either have: // - just a Concierge report, if so we'll return that // - their Concierge report, and a separate report that must have deeplinked them to the app before they created their account. // If it's the latter, we'll use the deeplinked report over the Concierge report, // since the Concierge report would be incorrectly selected over the deep-linked report in the logic below. - let sortedReports = sortReportsByLastRead(reports, reportMetadata, policyID, policyMemberAccountIDs); + + let reportsValues = Object.values(reports ?? {}) as Report[]; + + if (!!policyID || policyMemberAccountIDs.length > 0) { + reportsValues = filterReportsByPolicyIdAndMemberAccountIDs(reportsValues, policyID, policyMemberAccountIDs); + } + + let sortedReports = sortReportsByLastRead(reportsValues, reportMetadata); let adminReport: OnyxEntry | undefined; if (openOnAdminRoom) { diff --git a/tests/unit/ReportUtilsTest.js b/tests/unit/ReportUtilsTest.js index c9e8053e3146..23b748068b65 100644 --- a/tests/unit/ReportUtilsTest.js +++ b/tests/unit/ReportUtilsTest.js @@ -588,15 +588,15 @@ describe('ReportUtils', () => { describe('sortReportsByLastRead', () => { it('should filter out report without reportID & lastReadTime and sort lastReadTime in ascending order', () => { - const reports = { - 1: {reportID: 1, lastReadTime: '2023-07-08 07:15:44.030'}, - 2: {reportID: 2, lastReadTime: null}, - 3: {reportID: 3, lastReadTime: '2023-07-06 07:15:44.030'}, - 4: {reportID: 4, lastReadTime: '2023-07-07 07:15:44.030', type: CONST.REPORT.TYPE.IOU}, - 5: {lastReadTime: '2023-07-09 07:15:44.030'}, - 6: {reportID: 6}, - 7: {}, - }; + const reports = [ + {reportID: 1, lastReadTime: '2023-07-08 07:15:44.030'}, + {reportID: 2, lastReadTime: null}, + {reportID: 3, lastReadTime: '2023-07-06 07:15:44.030'}, + {reportID: 4, lastReadTime: '2023-07-07 07:15:44.030', type: CONST.REPORT.TYPE.IOU}, + {lastReadTime: '2023-07-09 07:15:44.030'}, + {reportID: 6}, + {}, + ]; const sortedReports = [ {reportID: 3, lastReadTime: '2023-07-06 07:15:44.030'}, {reportID: 4, lastReadTime: '2023-07-07 07:15:44.030', type: CONST.REPORT.TYPE.IOU}, From 2c9b08fb9196922e3ceb8284668c08c5a939dfea Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Thu, 18 Jan 2024 14:26:33 +0100 Subject: [PATCH 13/14] Refactor function params and descriptions --- src/libs/ReportUtils.ts | 6 +++--- src/libs/actions/Policy.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 062df807c61c..8e747ab8dafe 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -833,7 +833,7 @@ function isConciergeChatReport(report: OnyxEntry): boolean { /** * Checks if the supplied report belongs to workspace based on the provided params. If the report's policyID is _FAKE_ or has no value, it means this report is a DM. - * In this case report and workspace members must be compared to deteremine whether the report belongs to the workspace. + * In this case report and workspace members must be compared to determine whether the report belongs to the workspace. */ function doesReportBelongToWorkspace(report: Report, policyID: string, policyMemberAccountIDs: number[]) { return ( @@ -842,14 +842,14 @@ function doesReportBelongToWorkspace(report: Report, policyID: string, policyMem } /** - * Given a collection of reports, return them filtered by a policyID and policyMemberAccountIDs. + * Given an array of reports, return them filtered by a policyID and policyMemberAccountIDs. */ function filterReportsByPolicyIdAndMemberAccountIDs(reports: Report[], policyID = '', policyMemberAccountIDs: number[] = []) { return reports.filter((report) => !!report && doesReportBelongToWorkspace(report, policyID, policyMemberAccountIDs)); } /** - * Given a collection of reports, return them sorted by the last read timestamp. + * Given an array of reports, return them sorted by the last read timestamp. */ function sortReportsByLastRead(reports: Report[], reportMetadata: OnyxCollection): Array> { return reports diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 19ff4fb4700b..286f76ee6a77 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -1496,12 +1496,12 @@ function openWorkspace(policyID: string, clientMemberAccountIDs: number[]) { type OpenWorkspaceParams = { policyID: string; - clientMemberEmails: string; + clientMemberAccountIDs: string; }; const params: OpenWorkspaceParams = { policyID, - clientMemberEmails: JSON.stringify(clientMemberAccountIDs), + clientMemberAccountIDs: JSON.stringify(clientMemberAccountIDs), }; API.read('OpenWorkspace', params); From 76cda90d217f23a24aab1f8435a2f6ad28b042f3 Mon Sep 17 00:00:00 2001 From: Wojciech Boman Date: Thu, 18 Jan 2024 14:38:04 +0100 Subject: [PATCH 14/14] Add openWorkspace docs --- src/libs/actions/Policy.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/libs/actions/Policy.ts b/src/libs/actions/Policy.ts index 286f76ee6a77..cbbc00dd42fc 100644 --- a/src/libs/actions/Policy.ts +++ b/src/libs/actions/Policy.ts @@ -1488,6 +1488,9 @@ function openWorkspaceReimburseView(policyID: string) { API.read('OpenWorkspaceReimburseView', params, {successData, failureData}); } +/** + * Returns the accountIDs of the members of the policy whose data is passed in the parameters + */ function openWorkspace(policyID: string, clientMemberAccountIDs: number[]) { if (!policyID || !clientMemberAccountIDs) { Log.warn('openWorkspace invalid params', {policyID, clientMemberAccountIDs});