From 21c0ae56d1d318e620ae1e50ee97627ab105be99 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Wed, 27 Sep 2023 10:53:59 +0200 Subject: [PATCH 001/161] ref: started migrating Task lib to TS --- src/libs/actions/{Task.js => Task.ts} | 65 ++++++++++++--------------- src/types/onyx/Report.ts | 2 + 2 files changed, 31 insertions(+), 36 deletions(-) rename src/libs/actions/{Task.js => Task.ts} (93%) diff --git a/src/libs/actions/Task.js b/src/libs/actions/Task.ts similarity index 93% rename from src/libs/actions/Task.js rename to src/libs/actions/Task.ts index 963bfebb7eb2..b5c813e14521 100644 --- a/src/libs/actions/Task.js +++ b/src/libs/actions/Task.ts @@ -14,21 +14,22 @@ import * as ReportActionsUtils from '../ReportActionsUtils'; import * as Expensicons from '../../components/Icon/Expensicons'; import * as LocalePhoneNumber from '../LocalePhoneNumber'; import * as Localize from '../Localize'; +import {PersonalDetails, Report, Task} from '../../types/onyx'; -let currentUserEmail; -let currentUserAccountID; +let currentUserEmail: string; +let currentUserAccountID: number; Onyx.connect({ key: ONYXKEYS.SESSION, - callback: (val) => { - currentUserEmail = lodashGet(val, 'email', ''); - currentUserAccountID = lodashGet(val, 'accountID', 0); + callback: (value) => { + currentUserEmail = value?.email ?? ''; + currentUserAccountID = value?.accountID ?? 0; }, }); -let allPersonalDetails; +let allPersonalDetails: Record | null; Onyx.connect({ key: ONYXKEYS.PERSONAL_DETAILS_LIST, - callback: (val) => (allPersonalDetails = val), + callback: (value) => (allPersonalDetails = value), }); /** @@ -52,16 +53,16 @@ function clearOutTaskInfo() { * 3. The chat report between you and the assignee * 3a. The CreatedReportAction for the assignee chat report * 3b. The TaskReportAction on the assignee chat report - * - * @param {String} parentReportID - * @param {String} title - * @param {String} description - * @param {String} assigneeEmail - * @param {Number} assigneeAccountID - * @param {Object} assigneeChatReport - The chat report between you and the assignee - * @param {String} policyID - the policyID of the parent report */ -function createTaskAndNavigate(parentReportID, title, description, assigneeEmail, assigneeAccountID = 0, assigneeChatReport = null, policyID = CONST.POLICY.OWNER_EMAIL_FAKE) { +function createTaskAndNavigate( + parentReportID: string, + title: string, + description: string, + assigneeEmail: string, + assigneeAccountID = 0, + assigneeChatReport: Report | null = null, + policyID = CONST.POLICY.OWNER_EMAIL_FAKE, +) { const optimisticTaskReport = ReportUtils.buildOptimisticTaskReport(currentUserAccountID, assigneeAccountID, parentReportID, title, description, policyID); const assigneeChatReportID = assigneeChatReport ? assigneeChatReport.reportID : 0; @@ -203,12 +204,8 @@ function createTaskAndNavigate(parentReportID, title, description, assigneeEmail assignee: assigneeEmail, assigneeAccountID, assigneeChatReportID, - assigneeChatReportActionID: - assigneeChatReportOnyxData && assigneeChatReportOnyxData.optimisticAssigneeAddComment - ? assigneeChatReportOnyxData.optimisticAssigneeAddComment.reportAction.reportActionID - : 0, - assigneeChatCreatedReportActionID: - assigneeChatReportOnyxData && assigneeChatReportOnyxData.optimisticChatCreatedReportAction ? assigneeChatReportOnyxData.optimisticChatCreatedReportAction.reportActionID : 0, + assigneeChatReportActionID: assigneeChatReportOnyxData?.optimisticAssigneeAddComment.reportAction.reportActionID ?? 0, + assigneeChatCreatedReportActionID: assigneeChatReportOnyxData?.optimisticChatCreatedReportAction.reportActionID ?? 0, }, {optimisticData, successData, failureData}, ); @@ -305,7 +302,7 @@ function completeTask(taskReport, taskTitle) { * @param {Object} taskReport task report * @param {String} taskTitle Title of the task */ -function reopenTask(taskReport, taskTitle) { +function reopenTask(taskReport: Report, taskTitle: string) { const taskReportID = taskReport.reportID; const message = `reopened task: ${taskTitle}`; const reopenedTaskReportAction = ReportUtils.buildOptimisticTaskReportAction(taskReportID, CONST.REPORT.ACTIONS.TYPE.TASKREOPENED, message); @@ -393,15 +390,15 @@ function reopenTask(taskReport, taskTitle) { * @param {Object} editedTask * @param {Object} assigneeChatReport - The chat report between you and the assignee */ -function editTaskAndNavigate(report, ownerAccountID, {title, description, assignee = '', assigneeAccountID = 0}, assigneeChatReport = null) { +function editTaskAndNavigate(report: Report, ownerAccountID: number, {title, description, assignee = '', assigneeAccountID = 0}: Task, assigneeChatReport: Report | null = null) { // Create the EditedReportAction on the task const editTaskReportAction = ReportUtils.buildOptimisticEditedTaskReportAction(currentUserEmail); // Sometimes title or description is undefined, so we need to check for that, and we provide it to multiple functions - const reportName = (title || report.reportName).trim(); + const reportName = (title ?? report?.reportName).trim(); // Description can be unset, so we default to an empty string if so - const reportDescription = (!_.isUndefined(description) ? description : lodashGet(report, 'description', '')).trim(); + const reportDescription = (description ?? report.description ?? '').trim(); let assigneeChatReportOnyxData; const assigneeChatReportID = assigneeChatReport ? assigneeChatReport.reportID : 0; @@ -418,8 +415,8 @@ function editTaskAndNavigate(report, ownerAccountID, {title, description, assign value: { reportName, description: reportDescription, - managerID: assigneeAccountID || report.managerID, - managerEmail: assignee || report.managerEmail, + managerID: assigneeAccountID ?? report.managerID, + managerEmail: assignee ?? report.managerEmail, pendingFields: { ...(title && {reportName: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), ...(description && {description: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), @@ -487,12 +484,8 @@ function editTaskAndNavigate(report, ownerAccountID, {title, description, assign assigneeAccountID: assigneeAccountID || report.managerID, editedTaskReportActionID: editTaskReportAction.reportActionID, assigneeChatReportID, - assigneeChatReportActionID: - assigneeChatReportOnyxData && assigneeChatReportOnyxData.optimisticAssigneeAddComment - ? assigneeChatReportOnyxData.optimisticAssigneeAddComment.reportAction.reportActionID - : 0, - assigneeChatCreatedReportActionID: - assigneeChatReportOnyxData && assigneeChatReportOnyxData.optimisticChatCreatedReportAction ? assigneeChatReportOnyxData.optimisticChatCreatedReportAction.reportActionID : 0, + assigneeChatReportActionID: assigneeChatReportOnyxData.optimisticAssigneeAddComment.reportAction.reportActionID ?? 0, + assigneeChatCreatedReportActionID: assigneeChatReportOnyxData.optimisticChatCreatedReportAction.reportActionID ?? 0, }, {optimisticData, successData, failureData}, ); @@ -500,10 +493,10 @@ function editTaskAndNavigate(report, ownerAccountID, {title, description, assign Navigation.dismissModal(report.reportID); } -function editTaskAssigneeAndNavigate(report, ownerAccountID, assigneeEmail, assigneeAccountID = 0, assigneeChatReport = null) { +function editTaskAssigneeAndNavigate(report: Report, ownerAccountID: number, assigneeEmail: string, assigneeAccountID = 0, assigneeChatReport: Report | null = null) { // Create the EditedReportAction on the task const editTaskReportAction = ReportUtils.buildOptimisticEditedTaskReportAction(currentUserEmail); - const reportName = report.reportName.trim(); + const reportName = report.reportName?.trim(); let assigneeChatReportOnyxData; const assigneeChatReportID = assigneeChatReport ? assigneeChatReport.reportID : 0; diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts index 88caa683305d..2e944c156a64 100644 --- a/src/types/onyx/Report.ts +++ b/src/types/onyx/Report.ts @@ -83,6 +83,8 @@ type Report = { participantAccountIDs?: number[]; total?: number; currency?: string; + description?: string; + managerEmail?: string; }; export default Report; From 87998bf588a4fa826f06767b898f022111a5806d Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Wed, 27 Sep 2023 13:20:31 +0200 Subject: [PATCH 002/161] ref: move all methods to TS --- src/libs/actions/Task.ts | 155 +++++++++++++-------------------------- src/types/onyx/Report.ts | 1 + 2 files changed, 51 insertions(+), 105 deletions(-) diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index b5c813e14521..adc7c41f4cfe 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -1,6 +1,4 @@ import Onyx from 'react-native-onyx'; -import lodashGet from 'lodash/get'; -import _ from 'underscore'; import ONYXKEYS from '../../ONYXKEYS'; import * as API from '../API'; import * as ReportUtils from '../ReportUtils'; @@ -215,10 +213,8 @@ function createTaskAndNavigate( /** * Complete a task - * @param {Object} taskReport task report - * @param {String} taskTitle Title of the task */ -function completeTask(taskReport, taskTitle) { +function completeTask(taskReport: Report, taskTitle: string) { const taskReportID = taskReport.reportID; const message = `completed task: ${taskTitle}`; const completedTaskReportAction = ReportUtils.buildOptimisticTaskReportAction(taskReportID, CONST.REPORT.ACTIONS.TYPE.TASKCOMPLETED, message); @@ -251,6 +247,7 @@ function completeTask(taskReport, taskTitle) { }, }, ]; + const failureData = [ { onyxMethod: Onyx.METHOD.MERGE, @@ -274,7 +271,7 @@ function completeTask(taskReport, taskTitle) { // Multiple report actions can link to the same child. Both share destination (task parent) and assignee report link to the same report action. // We need to find and update the other parent report action (in assignee report). More info https://github.com/Expensify/App/issues/23920#issuecomment-1663092717 const assigneeReportAction = ReportUtils.getTaskParentReportActionIDInAssigneeReport(taskReport); - if (!_.isEmpty(assigneeReportAction)) { + if (Object.keys(assigneeReportAction).length > 0) { const optimisticDataForClonedParentReportAction = ReportUtils.getOptimisticDataForParentReportAction( taskReportID, completedTaskReportAction.created, @@ -282,7 +279,7 @@ function completeTask(taskReport, taskTitle) { assigneeReportAction.reportID, assigneeReportAction.reportActionID, ); - if (!_.isEmpty(optimisticDataForClonedParentReportAction)) { + if (Object.keys(optimisticDataForClonedParentReportAction).length > 0) { optimisticData.push(optimisticDataForClonedParentReportAction); } } @@ -299,8 +296,6 @@ function completeTask(taskReport, taskTitle) { /** * Reopen a closed task - * @param {Object} taskReport task report - * @param {String} taskTitle Title of the task */ function reopenTask(taskReport: Report, taskTitle: string) { const taskReportID = taskReport.reportID; @@ -361,7 +356,7 @@ function reopenTask(taskReport: Report, taskTitle: string) { // Multiple report actions can link to the same child. Both share destination (task parent) and assignee report link to the same report action. // We need to find and update the other parent report action (in assignee report). More info https://github.com/Expensify/App/issues/23920#issuecomment-1663092717 const assigneeReportAction = ReportUtils.getTaskParentReportActionIDInAssigneeReport(taskReport); - if (!_.isEmpty(assigneeReportAction)) { + if (Object.keys(assigneeReportAction).length > 0 && taskReportID) { const optimisticDataForClonedParentReportAction = ReportUtils.getOptimisticDataForParentReportAction( taskReportID, reopenedTaskReportAction.created, @@ -369,7 +364,7 @@ function reopenTask(taskReport: Report, taskTitle: string) { assigneeReportAction.reportID, assigneeReportAction.reportActionID, ); - if (!_.isEmpty(optimisticDataForClonedParentReportAction)) { + if (Object.keys(optimisticDataForClonedParentReportAction).length > 0) { optimisticData.push(optimisticDataForClonedParentReportAction); } } @@ -384,12 +379,6 @@ function reopenTask(taskReport: Report, taskTitle: string) { ); } -/** - * @param {object} report - * @param {Number} ownerAccountID - * @param {Object} editedTask - * @param {Object} assigneeChatReport - The chat report between you and the assignee - */ function editTaskAndNavigate(report: Report, ownerAccountID: number, {title, description, assignee = '', assigneeAccountID = 0}: Task, assigneeChatReport: Report | null = null) { // Create the EditedReportAction on the task const editTaskReportAction = ReportUtils.buildOptimisticEditedTaskReportAction(currentUserEmail); @@ -458,7 +447,7 @@ function editTaskAndNavigate(report: Report, ownerAccountID: number, {title, des // 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) { + if (assigneeAccountID && assigneeAccountID !== report.managerID && assigneeAccountID !== ownerAccountID && assigneeChatReport && report.reportID) { assigneeChatReportOnyxData = ReportUtils.getTaskAssigneeChatOnyxData( currentUserAccountID, assignee, @@ -480,12 +469,12 @@ function editTaskAndNavigate(report: Report, ownerAccountID: number, {title, des taskReportID: report.reportID, title: reportName, description: reportDescription, - assignee: assignee || report.managerEmail, - assigneeAccountID: assigneeAccountID || report.managerID, + assignee: assignee ?? report.managerEmail, + assigneeAccountID: assigneeAccountID ?? report.managerID, editedTaskReportActionID: editTaskReportAction.reportActionID, assigneeChatReportID, - assigneeChatReportActionID: assigneeChatReportOnyxData.optimisticAssigneeAddComment.reportAction.reportActionID ?? 0, - assigneeChatCreatedReportActionID: assigneeChatReportOnyxData.optimisticChatCreatedReportAction.reportActionID ?? 0, + assigneeChatReportActionID: assigneeChatReportOnyxData?.optimisticAssigneeAddComment.reportAction.reportActionID ?? 0, + assigneeChatCreatedReportActionID: assigneeChatReportOnyxData?.optimisticChatCreatedReportAction.reportActionID ?? 0, }, {optimisticData, successData, failureData}, ); @@ -512,8 +501,8 @@ function editTaskAssigneeAndNavigate(report: Report, ownerAccountID: number, ass key: `${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, value: { reportName, - managerID: assigneeAccountID || report.managerID, - managerEmail: assigneeEmail || report.managerEmail, + managerID: assigneeAccountID ?? report.managerID, + managerEmail: assigneeEmail ?? report.managerEmail, }, }, ]; @@ -533,7 +522,7 @@ function editTaskAssigneeAndNavigate(report: Report, ownerAccountID: number, ass // 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) { + if (assigneeAccountID && assigneeAccountID !== report.managerID && assigneeAccountID !== ownerAccountID && assigneeChatReport && report.reportID) { assigneeChatReportOnyxData = ReportUtils.getTaskAssigneeChatOnyxData( currentUserAccountID, assigneeEmail, @@ -553,16 +542,12 @@ function editTaskAssigneeAndNavigate(report: Report, ownerAccountID: number, ass 'EditTaskAssignee', { taskReportID: report.reportID, - assignee: assigneeEmail || report.managerEmail, - assigneeAccountID: assigneeAccountID || report.managerID, + assignee: assigneeEmail ?? report.managerEmail, + assigneeAccountID: assigneeAccountID ?? report.managerID, editedTaskReportActionID: editTaskReportAction.reportActionID, assigneeChatReportID, - assigneeChatReportActionID: - assigneeChatReportOnyxData && assigneeChatReportOnyxData.optimisticAssigneeAddComment - ? assigneeChatReportOnyxData.optimisticAssigneeAddComment.reportAction.reportActionID - : 0, - assigneeChatCreatedReportActionID: - assigneeChatReportOnyxData && assigneeChatReportOnyxData.optimisticChatCreatedReportAction ? assigneeChatReportOnyxData.optimisticChatCreatedReportAction.reportActionID : 0, + assigneeChatReportActionID: assigneeChatReportOnyxData?.optimisticAssigneeAddComment.reportAction.reportActionID ?? 0, + assigneeChatCreatedReportActionID: assigneeChatReportOnyxData?.optimisticChatCreatedReportAction.reportActionID ?? 0, }, {optimisticData, successData, failureData}, ); @@ -575,49 +560,43 @@ function editTaskAssigneeAndNavigate(report: Report, ownerAccountID: number, ass * * @param {Object} report */ -function setTaskReport(report) { +function setTaskReport(report: Report) { Onyx.merge(ONYXKEYS.TASK, {report}); } /** * Sets the title and description values for the task - * @param {string} title - * @param {string} description */ -function setDetailsValue(title, description) { +function setDetailsValue(title: string, description: string) { // This is only needed for creation of a new task and so it should only be stored locally Onyx.merge(ONYXKEYS.TASK, {title: title.trim(), description: description.trim()}); } /** * Sets the title value for the task - * @param {string} title */ -function setTitleValue(title) { +function setTitleValue(title: string) { Onyx.merge(ONYXKEYS.TASK, {title: title.trim()}); } /** * Sets the description value for the task - * @param {string} description */ -function setDescriptionValue(description) { +function setDescriptionValue(description: string) { Onyx.merge(ONYXKEYS.TASK, {description: description.trim()}); } /** * Sets the shareDestination value for the task - * @param {string} shareDestination */ -function setShareDestinationValue(shareDestination) { +function setShareDestinationValue(shareDestination: string) { // This is only needed for creation of a new task and so it should only be stored locally Onyx.merge(ONYXKEYS.TASK, {shareDestination}); } /* Sets the assigneeChatReport details for the task - * @param {Object} chatReport */ -function setAssigneeChatReport(chatReport) { +function setAssigneeChatReport(chatReport: Report) { Onyx.merge(ONYXKEYS.TASK, {assigneeChatReport: chatReport}); } @@ -625,14 +604,9 @@ function setAssigneeChatReport(chatReport) { * Sets the assignee value for the task and checks for an existing chat with the assignee * If there is no existing chat, it creates an optimistic chat report * It also sets the shareDestination as that chat report if a share destination isn't already set - * @param {string} assigneeEmail - * @param {Number} assigneeAccountID - * @param {string} shareDestination - * @param {boolean} isCurrentUser */ - -function setAssigneeValue(assigneeEmail, assigneeAccountID, shareDestination, isCurrentUser = false) { - let chatReport; +function setAssigneeValue(assigneeEmail: string, assigneeAccountID: number, shareDestination: string, isCurrentUser = false) { + let chatReport: Report | undefined; if (!isCurrentUser) { chatReport = ReportUtils.getChatByParticipants([assigneeAccountID]); @@ -644,14 +618,16 @@ function setAssigneeValue(assigneeEmail, assigneeAccountID, shareDestination, is // However, the DM doesn't exist yet - and will be created optimistically once the task is created // We don't want to show the new DM yet, because if you select an assignee and then change the assignee, the previous DM will still be shown // So here, we create it optimistically to share it with the assignee, but we have to hide it until the task is created - chatReport.isHidden = true; + if (chatReport) { + chatReport.isHidden = true; + } Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, chatReport); // If this is an optimistic report, we likely don't have their personal details yet so we set it here optimistically as well const optimisticPersonalDetailsListAction = { accountID: assigneeAccountID, - avatar: lodashGet(allPersonalDetails, [assigneeAccountID, 'avatar'], UserUtils.getDefaultAvatarURL(assigneeAccountID)), - displayName: lodashGet(allPersonalDetails, [assigneeAccountID, 'displayName'], assigneeEmail), + avatar: allPersonalDetails?.[assigneeAccountID]?.avatar ?? UserUtils.getDefaultAvatarURL(assigneeAccountID), + displayName: allPersonalDetails?.[assigneeAccountID]?.displayName ?? assigneeEmail, login: assigneeEmail, }; Onyx.merge(ONYXKEYS.PERSONAL_DETAILS_LIST, {[assigneeAccountID]: optimisticPersonalDetailsListAction}); @@ -662,7 +638,7 @@ function setAssigneeValue(assigneeEmail, assigneeAccountID, shareDestination, is // If there is no share destination set, automatically set it to the assignee chat report // This allows for a much quicker process when creating a new task and is likely the desired share destination most times if (!shareDestination) { - setShareDestinationValue(chatReport.reportID); + setShareDestinationValue(chatReport?.reportID ?? ''); } } @@ -677,31 +653,25 @@ function setAssigneeValue(assigneeEmail, assigneeAccountID, shareDestination, is /** * Sets the parentReportID value for the task - * @param {string} parentReportID */ -function setParentReportID(parentReportID) { +function setParentReportID(parentReportID: string) { // This is only needed for creation of a new task and so it should only be stored locally Onyx.merge(ONYXKEYS.TASK, {parentReportID}); } /** * Clears out the task info from the store and navigates to the NewTaskDetails page - * @param {string} reportID */ -function clearOutTaskInfoAndNavigate(reportID) { +function clearOutTaskInfoAndNavigate(reportID: string) { clearOutTaskInfo(); setParentReportID(reportID); - Navigation.navigate(ROUTES.NEW_TASK_DETAILS); + Navigation.navigate(ROUTES.NEW_TASK_DETAILS, ''); } /** * Get the assignee data - * - * @param {Number} assigneeAccountID - * @param {Object} personalDetails - * @returns {Object} */ -function getAssignee(assigneeAccountID, personalDetails) { +function getAssignee(assigneeAccountID: number, personalDetails: Record) { const details = personalDetails[assigneeAccountID]; if (!details) { return { @@ -719,18 +689,15 @@ function getAssignee(assigneeAccountID, personalDetails) { /** * Get the share destination data - * @param {Object} reportID - * @param {Object} reports - * @param {Object} personalDetails - * @returns {Object} * */ -function getShareDestination(reportID, reports, personalDetails) { - const report = lodashGet(reports, `report_${reportID}`, {}); +function getShareDestination(reportID: string, reports: Record, personalDetails: Record) { + const report = reports[`report_${reportID}`] ?? {}; let subtitle = ''; if (ReportUtils.isChatReport(report) && ReportUtils.isDM(report) && ReportUtils.hasSingleParticipant(report)) { - const participantAccountID = lodashGet(report, 'participantAccountIDs[0]'); - const displayName = lodashGet(personalDetails, [participantAccountID, 'displayName']); - const login = lodashGet(personalDetails, [participantAccountID, 'login']); + const participantAccountID = report.participantAccountIDs?.[0]; + + const displayName = personalDetails[participantAccountID ?? 0]?.displayName ?? ''; + const login = personalDetails[participantAccountID ?? 0]?.login ?? ''; subtitle = LocalePhoneNumber.formatPhoneNumber(login || displayName); } else { subtitle = ReportUtils.getChatRoomSubtitle(report); @@ -744,12 +711,8 @@ function getShareDestination(reportID, reports, personalDetails) { /** * Cancels a task by setting the report state to SUBMITTED and status to CLOSED - * @param {string} taskReportID - * @param {string} taskTitle - * @param {number} originalStateNum - * @param {number} originalStatusNum */ -function cancelTask(taskReportID, taskTitle, originalStateNum, originalStatusNum) { +function cancelTask(taskReportID: string, taskTitle: string, originalStateNum: number, originalStatusNum: number) { const message = `deleted task: ${taskTitle}`; const optimisticCancelReportAction = ReportUtils.buildOptimisticTaskReportAction(taskReportID, CONST.REPORT.ACTIONS.TYPE.TASKCANCELLED, message); const optimisticReportActionID = optimisticCancelReportAction.reportActionID; @@ -853,11 +816,8 @@ function dismissModalAndClearOutTaskInfo() { /** * Returns Task assignee accountID - * - * @param {Object} taskReport - * @returns {Number|null} */ -function getTaskAssigneeAccountID(taskReport) { +function getTaskAssigneeAccountID(taskReport: Report): number | null { if (!taskReport) { return null; } @@ -867,26 +827,20 @@ function getTaskAssigneeAccountID(taskReport) { } const reportAction = ReportActionsUtils.getParentReportAction(taskReport); - return lodashGet(reportAction, 'childManagerAccountID'); + return reportAction.childManagerAccountID; } /** * Returns Task owner accountID - * - * @param {Object} taskReport - * @returns {Number|null} */ -function getTaskOwnerAccountID(taskReport) { - return lodashGet(taskReport, 'ownerAccountID', null); +function getTaskOwnerAccountID(taskReport: Report): number | null { + return taskReport.ownerAccountID ?? null; } /** * Check if you're allowed to modify the task - anyone that has write access to the report can modify the task - * @param {Object} taskReport - * @param {Number} sessionAccountID - * @returns {Boolean} */ -function canModifyTask(taskReport, sessionAccountID) { +function canModifyTask(taskReport: Report, sessionAccountID: number): boolean { if (sessionAccountID === getTaskOwnerAccountID(taskReport) || sessionAccountID === getTaskAssigneeAccountID(taskReport)) { return true; } @@ -898,23 +852,14 @@ function canModifyTask(taskReport, sessionAccountID) { return ReportUtils.isAllowedToComment(parentReport); } -/** - * @param {String} reportID - */ -function clearEditTaskErrors(reportID) { +function clearEditTaskErrors(reportID: string) { Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, { pendingFields: null, errorFields: null, }); } -/** - * @param {string} actionName - * @param {string} reportID - * @param {boolean} isCreateTaskAction - * @returns {string} - */ -function getTaskReportActionMessage(actionName, reportID, isCreateTaskAction) { +function getTaskReportActionMessage(actionName: string, reportID: string, isCreateTaskAction: boolean): string { const report = ReportUtils.getReport(reportID); if (isCreateTaskAction) { return `Created a task: ${report.reportName}`; diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts index 2e944c156a64..ccb33f60531e 100644 --- a/src/types/onyx/Report.ts +++ b/src/types/onyx/Report.ts @@ -85,6 +85,7 @@ type Report = { currency?: string; description?: string; managerEmail?: string; + isHidden?: boolean; }; export default Report; From a917e0644a85e5593624dc9a052b5155c9dcc93e Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Thu, 16 Nov 2023 09:32:54 -0500 Subject: [PATCH 003/161] adds violations functions to report utils and implements --- .../ReportActionItem/ReportPreview.js | 2 +- src/libs/ReportUtils.js | 78 +++++++++++++++++++ src/libs/SidebarUtils.ts | 2 +- 3 files changed, 80 insertions(+), 2 deletions(-) diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index 45fe7d42e299..38fc7b008095 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -125,7 +125,7 @@ function ReportPreview(props) { const hasReceipts = transactionsWithReceipts.length > 0; const hasOnlyDistanceRequests = ReportUtils.hasOnlyDistanceRequestTransactions(props.iouReportID); const isScanning = hasReceipts && ReportUtils.areAllRequestsBeingSmartScanned(props.iouReportID, props.action); - const hasErrors = hasReceipts && ReportUtils.hasMissingSmartscanFields(props.iouReportID); + const hasErrors = (hasReceipts && ReportUtils.hasMissingSmartscanFields(props.iouReportID)) || ReportUtils.reportHasViolations(props.iouReportID); const lastThreeTransactionsWithReceipts = transactionsWithReceipts.slice(-3); const lastThreeReceipts = _.map(lastThreeTransactionsWithReceipts, (transaction) => ReceiptUtils.getThumbnailAndImageURIs(transaction)); const hasNonReimbursableTransactions = ReportUtils.hasNonReimbursableTransactions(props.iouReportID); diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 973ed1f0dfd1..12808ef747a5 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -12,6 +12,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import * as IOU from './actions/IOU'; +import * as CollectionUtils from './CollectionUtils'; import * as CurrencyUtils from './CurrencyUtils'; import DateUtils from './DateUtils'; import isReportMessageAttachment from './isReportMessageAttachment'; @@ -81,6 +82,19 @@ Onyx.connect({ callback: (val) => (loginList = val), }); +const transactionViolations = {}; +Onyx.connect({ + key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, + callback: (violations, key) => { + if (!key || !violations) { + return; + } + + const transactionID = CollectionUtils.extractCollectionItemID(key); + transactionViolations[transactionID] = violations; + }, +}); + function getChatType(report) { return report ? report.chatType : ''; } @@ -3294,6 +3308,63 @@ function shouldHideReport(report, currentReportId) { return parentReport.reportID !== report.reportID && !isChildReportHasComment; } +/** + * @param {String} transactionID + * @returns {Boolean} + */ + +function transactionHasViolation(transactionID) { + const violations = lodashGet(transactionViolations, transactionID, []); + return _.some(violations, (violation) => violation.type === 'violation'); +} + +/** + * + * @param {Object} report + * @returns {Boolean} + */ + +function transactionThreadHasViolations(report) { + if (!Permissions.canUseViolations()) { + return false; + } + // eslint-disable-next-line es/no-nullish-coalescing-operators + if (!report.parentReportActionID ?? 0) { + return false; + } + + const reportActions = ReportActionsUtils.getAllReportActions(report.reportID); + + const parentReportAction = lodashGet(reportActions, `${report.parentReportID}.${report.parentReportActionID}`); + if (!parentReportAction) { + return false; + } + // eslint-disable-next-line es/no-nullish-coalescing-operators + const transactionID = parentReportAction.originalMessage.IOUTransactionID ?? 0; + if (!transactionID) { + return false; + } + // eslint-disable-next-line es/no-nullish-coalescing-operators + const reportID = parentReportAction.originalMessage.IOUReportID ?? 0; + if (!reportID) { + return false; + } + if (!isCurrentUserSubmitter(reportID)) { + return false; + } + return transactionHasViolation(transactionID); +} + +/** + * @param {String} reportID + * @returns {Boolean} + */ + +function reportHasViolations(reportID) { + const transactions = TransactionUtils.getAllReportTransactions(reportID); + return _.some(transactions, (transaction) => transactionHasViolation(transaction.transactionID)); +} + /** * Takes several pieces of data from Onyx and evaluates if a report should be shown in the option list (either when searching * for reports or the reports shown in the LHN). @@ -3369,6 +3440,11 @@ function shouldReportBeInOptionList(report, currentReportId, isInGSDMode, betas, return true; } + // Always show IOU reports with violations + if (isExpenseRequest(report) && transactionThreadHasViolations(report)) { + return true; + } + // All unread chats (even archived ones) in GSD mode will be shown. This is because GSD mode is specifically for focusing the user on the most relevant chats, primarily, the unread ones if (isInGSDMode) { return isUnread(report); @@ -4358,4 +4434,6 @@ export { getReimbursementQueuedActionMessage, getPersonalDetailsForAccountID, getRoom, + transactionThreadHasViolations, + reportHasViolations, }; diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 58c4a124335d..2940a6286a32 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -361,7 +361,7 @@ function getOptionData( result.shouldShowSubscript = ReportUtils.shouldReportShowSubscript(report); result.pendingAction = report.pendingFields ? report.pendingFields.addWorkspaceRoom || report.pendingFields.createChat : null; result.allReportErrors = OptionsListUtils.getAllReportErrors(report, reportActions) as OnyxCommon.Errors; - result.brickRoadIndicator = Object.keys(result.allReportErrors ?? {}).length !== 0 ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''; + result.brickRoadIndicator = Object.keys(result.allReportErrors ?? {}).length !== 0 || ReportUtils.transactionThreadHasViolations(report) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''; result.ownerAccountID = report.ownerAccountID; result.managerID = report.managerID; result.reportID = report.reportID; From 3756ef9c53ee0ba0fb0a93d8a4f8a8c5f89750b7 Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Thu, 16 Nov 2023 11:03:03 -0500 Subject: [PATCH 004/161] Small fix in ReportUtils --- src/libs/ReportUtils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 12808ef747a5..6fe0ff8ad7ea 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -3329,7 +3329,7 @@ function transactionThreadHasViolations(report) { return false; } // eslint-disable-next-line es/no-nullish-coalescing-operators - if (!report.parentReportActionID ?? 0) { + if (!report.parentReportActionID) { return false; } From 3cc2e0f6bcc8e9ffbf16127831ea4fc8faebae0d Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Thu, 16 Nov 2023 15:15:20 -0500 Subject: [PATCH 005/161] Fix Onyx Connection for reportActions --- src/libs/ReportUtils.js | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 6fe0ff8ad7ea..e90f2eb955d4 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -95,6 +95,19 @@ Onyx.connect({ }, }); +const reportActions = {}; +Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, + callback: (actions, key) => { + if (!key || !actions) { + return; + } + + const reportID = CollectionUtils.extractCollectionItemID(key); + reportActions[reportID] = actions; + }, +}); + function getChatType(report) { return report ? report.chatType : ''; } @@ -3303,8 +3316,8 @@ function canAccessReport(report, policies, betas, allReportActions) { */ function shouldHideReport(report, currentReportId) { const parentReport = getParentReport(getReport(currentReportId)); - const reportActions = ReportActionsUtils.getAllReportActions(report.reportID); - const isChildReportHasComment = _.some(reportActions, (reportAction) => (reportAction.childVisibleActionCount || 0) > 0); + const allReportActions = ReportActionsUtils.getAllReportActions(report.reportID); + const isChildReportHasComment = _.some(allReportActions, (reportAction) => (reportAction.childVisibleActionCount || 0) > 0); return parentReport.reportID !== report.reportID && !isChildReportHasComment; } @@ -3333,8 +3346,6 @@ function transactionThreadHasViolations(report) { return false; } - const reportActions = ReportActionsUtils.getAllReportActions(report.reportID); - const parentReportAction = lodashGet(reportActions, `${report.parentReportID}.${report.parentReportActionID}`); if (!parentReportAction) { return false; From a98b3a12290754acc7610c3e84083a9ac9f515a6 Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Tue, 21 Nov 2023 12:17:38 -0500 Subject: [PATCH 006/161] Fix Permissions import --- src/libs/ReportUtils.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index cc7e841de251..78f636a7ad74 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -8,6 +8,8 @@ import Onyx from 'react-native-onyx'; import _ from 'underscore'; import * as Expensicons from '@components/Icon/Expensicons'; import * as defaultWorkspaceAvatars from '@components/Icon/WorkspaceDefaultAvatars'; +// eslint-disable-next-line @dword-design/import-alias/prefer-alias +import Permissions from '@libs/Permissions'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -20,7 +22,6 @@ import * as Localize from './Localize'; import linkingConfig from './Navigation/linkingConfig'; import Navigation from './Navigation/Navigation'; import * as NumberUtils from './NumberUtils'; -import Permissions from './Permissions'; import * as PolicyUtils from './PolicyUtils'; import * as ReportActionsUtils from './ReportActionsUtils'; import * as TransactionUtils from './TransactionUtils'; From f7dc7311cba335f50fc44fa36616a9e082f121c1 Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Wed, 22 Nov 2023 09:50:25 -0500 Subject: [PATCH 007/161] Correctly pass down betas --- src/components/LHNOptionsList/LHNOptionsList.js | 11 ++++++++++- src/components/LHNOptionsList/OptionRowLHNData.js | 2 +- src/libs/ReportUtils.js | 10 +++++----- src/libs/SidebarUtils.ts | 4 +++- 4 files changed, 19 insertions(+), 8 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.js b/src/components/LHNOptionsList/LHNOptionsList.js index 0d300c5e2179..605ad761240a 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.js +++ b/src/components/LHNOptionsList/LHNOptionsList.js @@ -64,6 +64,9 @@ const propTypes = { transactions: PropTypes.objectOf(transactionPropTypes), /** List of draft comments */ draftComments: PropTypes.objectOf(PropTypes.string), + + /** The list of betas the user has access to */ + betas: PropTypes.arrayOf(PropTypes.string), ...withCurrentReportIDPropTypes, }; @@ -77,6 +80,7 @@ const defaultProps = { personalDetails: {}, transactions: {}, draftComments: {}, + betas: {}, ...withCurrentReportIDDefaultProps, }; @@ -97,6 +101,7 @@ function LHNOptionsList({ transactions, draftComments, currentReportID, + betas, }) { const styles = useThemeStyles(); /** @@ -134,10 +139,11 @@ function LHNOptionsList({ onSelectRow={onSelectRow} preferredLocale={preferredLocale} comment={itemComment} + betas={betas} /> ); }, - [currentReportID, draftComments, onSelectRow, optionMode, personalDetails, policy, preferredLocale, reportActions, reports, shouldDisableFocusOptions, transactions], + [currentReportID, draftComments, onSelectRow, optionMode, personalDetails, policy, preferredLocale, reportActions, reports, shouldDisableFocusOptions, transactions, betas], ); return ( @@ -186,5 +192,8 @@ export default compose( draftComments: { key: ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, }, + betas: { + key: ONYXKEYS.BETAS, + }, }), )(LHNOptionsList); diff --git a/src/components/LHNOptionsList/OptionRowLHNData.js b/src/components/LHNOptionsList/OptionRowLHNData.js index e11bfc3cab98..f3b0e78b0df0 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.js +++ b/src/components/LHNOptionsList/OptionRowLHNData.js @@ -87,7 +87,7 @@ function OptionRowLHNData({ const optionItem = useMemo(() => { // Note: ideally we'd have this as a dependent selector in onyx! - const item = SidebarUtils.getOptionData(fullReport, reportActions, personalDetails, preferredLocale, policy, parentReportAction); + const item = SidebarUtils.getOptionData(fullReport, reportActions, personalDetails, preferredLocale, policy, parentReportAction, propsToForward.betas); if (deepEqual(item, optionItemRef.current)) { return optionItemRef.current; } diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 78f636a7ad74..bac1b45440c3 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -8,8 +8,6 @@ import Onyx from 'react-native-onyx'; import _ from 'underscore'; import * as Expensicons from '@components/Icon/Expensicons'; import * as defaultWorkspaceAvatars from '@components/Icon/WorkspaceDefaultAvatars'; -// eslint-disable-next-line @dword-design/import-alias/prefer-alias -import Permissions from '@libs/Permissions'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -22,6 +20,7 @@ import * as Localize from './Localize'; import linkingConfig from './Navigation/linkingConfig'; import Navigation from './Navigation/Navigation'; import * as NumberUtils from './NumberUtils'; +import Permissions from './Permissions'; import * as PolicyUtils from './PolicyUtils'; import * as ReportActionsUtils from './ReportActionsUtils'; import * as TransactionUtils from './TransactionUtils'; @@ -3361,14 +3360,15 @@ function transactionHasViolation(transactionID) { /** * * @param {Object} report + * @param {Array | null} betas * @returns {Boolean} */ -function transactionThreadHasViolations(report) { - if (!Permissions.canUseViolations()) { +function transactionThreadHasViolations(report, betas) { + if (!Permissions.canUseViolations(betas)) { return false; } - // eslint-disable-next-line es/no-nullish-coalescing-operators + if (!report.parentReportActionID) { return false; } diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 2940a6286a32..510ca2347a14 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -299,6 +299,7 @@ function getOptionData( preferredLocale: ValueOf, policy: Policy, parentReportAction: ReportAction, + betas: Beta[], ): OptionData | undefined { // When a user signs out, Onyx is cleared. Due to the lazy rendering with a virtual list, it's possible for // this method to be called after the Onyx data has been cleared out. In that case, it's fine to do @@ -361,7 +362,8 @@ function getOptionData( result.shouldShowSubscript = ReportUtils.shouldReportShowSubscript(report); result.pendingAction = report.pendingFields ? report.pendingFields.addWorkspaceRoom || report.pendingFields.createChat : null; result.allReportErrors = OptionsListUtils.getAllReportErrors(report, reportActions) as OnyxCommon.Errors; - result.brickRoadIndicator = Object.keys(result.allReportErrors ?? {}).length !== 0 || ReportUtils.transactionThreadHasViolations(report) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''; + result.brickRoadIndicator = + Object.keys(result.allReportErrors ?? {}).length !== 0 || ReportUtils.transactionThreadHasViolations(report, betas) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''; result.ownerAccountID = report.ownerAccountID; result.managerID = report.managerID; result.reportID = report.reportID; From 93ea5b7de9f4b9ef3409e1250549590ae1eabc29 Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Wed, 22 Nov 2023 13:38:37 -0500 Subject: [PATCH 008/161] Update tests --- src/libs/__mocks__/Permissions.ts | 1 + tests/unit/SidebarFilterTest.js | 11 ++++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/libs/__mocks__/Permissions.ts b/src/libs/__mocks__/Permissions.ts index e95d13f52803..23939d037f9a 100644 --- a/src/libs/__mocks__/Permissions.ts +++ b/src/libs/__mocks__/Permissions.ts @@ -13,4 +13,5 @@ export default { canUseDefaultRooms: (betas: Beta[]) => betas.includes(CONST.BETAS.DEFAULT_ROOMS), canUsePolicyRooms: (betas: Beta[]) => betas.includes(CONST.BETAS.POLICY_ROOMS), canUseCustomStatus: (betas: Beta[]) => betas.includes(CONST.BETAS.CUSTOM_STATUS), + canUseViolations: (betas: Beta[]) => betas.includes(CONST.BETAS.VIOLATIONS), }; diff --git a/tests/unit/SidebarFilterTest.js b/tests/unit/SidebarFilterTest.js index 23a958e3aa9d..f4a4a64a3907 100644 --- a/tests/unit/SidebarFilterTest.js +++ b/tests/unit/SidebarFilterTest.js @@ -111,6 +111,7 @@ describe('Sidebar', () => { // When Onyx is updated to contain that report .then(() => Onyx.multiSet({ + [ONYXKEYS.BETAS]: [CONST.BETAS.VIOLATIONS], [`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`]: report, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_REPORT_DATA]: false, @@ -141,7 +142,7 @@ describe('Sidebar', () => { // When Onyx is updated to contain that data and the sidebar re-renders .then(() => Onyx.multiSet({ - [ONYXKEYS.BETAS]: [], + [ONYXKEYS.BETAS]: [CONST.BETAS.VIOLATIONS], [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_REPORT_DATA]: false, [`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`]: report, @@ -194,7 +195,7 @@ describe('Sidebar', () => { // When Onyx is updated to contain that data and the sidebar re-renders .then(() => Onyx.multiSet({ - [ONYXKEYS.BETAS]: [], + [ONYXKEYS.BETAS]: [CONST.BETAS.VIOLATIONS], [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_REPORT_DATA]: false, [`${ONYXKEYS.COLLECTION.REPORT}${report1.reportID}`]: report1, @@ -246,7 +247,7 @@ describe('Sidebar', () => { // When Onyx is updated to contain that data and the sidebar re-renders .then(() => Onyx.multiSet({ - [ONYXKEYS.BETAS]: [], + [ONYXKEYS.BETAS]: [CONST.BETAS.VIOLATIONS], [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_REPORT_DATA]: false, [`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`]: report, @@ -379,6 +380,7 @@ describe('Sidebar', () => { // When Onyx is updated to contain that data and the sidebar re-renders .then(() => Onyx.multiSet({ + [ONYXKEYS.BETAS]: [CONST.BETAS.VIOLATIONS], [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.GSD, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_REPORT_DATA]: false, @@ -445,11 +447,14 @@ describe('Sidebar', () => { }; LHNTestUtils.getDefaultRenderedSidebarLinks(draftReport.reportID); + const betas = [CONST.BETAS.VIOLATIONS]; + return ( waitForBatchedUpdates() // When Onyx is updated to contain that data and the sidebar re-renders .then(() => Onyx.multiSet({ + [ONYXKEYS.BETAS]: betas, [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.GSD, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_REPORT_DATA]: false, From 1f9d6458b700bb00e5e1fb68fcda8171ee2b7643 Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Mon, 27 Nov 2023 13:19:05 -0500 Subject: [PATCH 009/161] Fixing some beta props --- src/components/LHNOptionsList/OptionRowLHNData.js | 5 +++-- src/libs/ReportUtils.js | 4 ++-- tests/unit/SidebarFilterTest.js | 4 +--- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/components/LHNOptionsList/OptionRowLHNData.js b/src/components/LHNOptionsList/OptionRowLHNData.js index f3b0e78b0df0..765c4998adce 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.js +++ b/src/components/LHNOptionsList/OptionRowLHNData.js @@ -73,6 +73,7 @@ function OptionRowLHNData({ receiptTransactions, parentReportAction, transaction, + betas, ...propsToForward }) { const reportID = propsToForward.reportID; @@ -87,7 +88,7 @@ function OptionRowLHNData({ const optionItem = useMemo(() => { // Note: ideally we'd have this as a dependent selector in onyx! - const item = SidebarUtils.getOptionData(fullReport, reportActions, personalDetails, preferredLocale, policy, parentReportAction, propsToForward.betas); + const item = SidebarUtils.getOptionData(fullReport, reportActions, personalDetails, preferredLocale, policy, parentReportAction, betas); if (deepEqual(item, optionItemRef.current)) { return optionItemRef.current; } @@ -96,7 +97,7 @@ function OptionRowLHNData({ // Listen parentReportAction to update title of thread report when parentReportAction changed // Listen to transaction to update title of transaction report when transaction changed // eslint-disable-next-line react-hooks/exhaustive-deps - }, [fullReport, linkedTransaction, reportActions, personalDetails, preferredLocale, policy, parentReportAction, transaction]); + }, [fullReport, linkedTransaction, reportActions, personalDetails, preferredLocale, policy, parentReportAction, transaction, betas]); useEffect(() => { if (!optionItem || optionItem.hasDraftComment || !comment || comment.length <= 0 || isFocused) { diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 32588d9d6f97..e9362bc8afcc 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -8,6 +8,8 @@ import Onyx from 'react-native-onyx'; import _ from 'underscore'; import * as Expensicons from '@components/Icon/Expensicons'; import * as defaultWorkspaceAvatars from '@components/Icon/WorkspaceDefaultAvatars'; +// eslint-disable-next-line @dword-design/import-alias/prefer-alias +import Permissions from '@libs/Permissions'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -20,7 +22,6 @@ import * as Localize from './Localize'; import linkingConfig from './Navigation/linkingConfig'; import Navigation from './Navigation/Navigation'; import * as NumberUtils from './NumberUtils'; -import Permissions from './Permissions'; import * as PolicyUtils from './PolicyUtils'; import * as ReportActionsUtils from './ReportActionsUtils'; import * as TransactionUtils from './TransactionUtils'; @@ -3399,7 +3400,6 @@ function transactionThreadHasViolations(report, betas) { if (!Permissions.canUseViolations(betas)) { return false; } - if (!report.parentReportActionID) { return false; } diff --git a/tests/unit/SidebarFilterTest.js b/tests/unit/SidebarFilterTest.js index f4a4a64a3907..f54b2a6f5c95 100644 --- a/tests/unit/SidebarFilterTest.js +++ b/tests/unit/SidebarFilterTest.js @@ -447,14 +447,12 @@ describe('Sidebar', () => { }; LHNTestUtils.getDefaultRenderedSidebarLinks(draftReport.reportID); - const betas = [CONST.BETAS.VIOLATIONS]; - return ( waitForBatchedUpdates() // When Onyx is updated to contain that data and the sidebar re-renders .then(() => Onyx.multiSet({ - [ONYXKEYS.BETAS]: betas, + [ONYXKEYS.BETAS]: [CONST.BETAS.VIOLATIONS], [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.GSD, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_REPORT_DATA]: false, From 6d47be552a2266c9c57c7768716790a7675e095a Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Mon, 27 Nov 2023 16:34:41 -0500 Subject: [PATCH 010/161] Started TS conversion --- src/ONYXKEYS.ts | 3 +++ src/libs/ReportUtils.ts | 7 ++--- src/types/onyx/TransactionViolation.ts | 36 ++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 3 deletions(-) create mode 100644 src/types/onyx/TransactionViolation.ts diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 5576eb64736d..cf98433da76d 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -266,6 +266,9 @@ const ONYXKEYS = { SECURITY_GROUP: 'securityGroup_', TRANSACTION: 'transactions_', + // Transaction Violations + TRANSACTION_VIOLATIONS: 'transactionViolations_', + // Holds temporary transactions used during the creation and edit flow TRANSACTION_DRAFT: 'transactionsDraft_', SPLIT_TRANSACTION_DRAFT: 'splitTransactionDraft_', diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 8beeff76c5da..7cd21e19ab0f 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -21,6 +21,7 @@ import {Errors, Icon, PendingAction} from '@src/types/onyx/OnyxCommon'; import {ChangeLog, IOUMessage, OriginalMessageActionName} from '@src/types/onyx/OriginalMessage'; import {Message, ReportActions} from '@src/types/onyx/ReportAction'; import {Receipt, WaypointCollection} from '@src/types/onyx/Transaction'; +import {TransactionViolation, TransactionViolations} from '@src/types/onyx/TransactionViolation'; import DeepValueOf from '@src/types/utils/DeepValueOf'; import {EmptyObject, isEmptyObject, isNotEmptyObject} from '@src/types/utils/EmptyObject'; import * as CollectionUtils from './CollectionUtils'; @@ -402,10 +403,10 @@ Onyx.connect({ callback: (value) => (loginList = value), }); -const transactionViolations = {}; +const transactionViolations: OnyxCollection = {}; Onyx.connect({ key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, - callback: (violations, key) => { + callback: (violations: OnyxCollection, key) => { if (!key || !violations) { return; } @@ -415,7 +416,7 @@ Onyx.connect({ }, }); -const reportActions = {}; +const reportActions: OnyxCollection = {}; Onyx.connect({ key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, callback: (actions, key) => { diff --git a/src/types/onyx/TransactionViolation.ts b/src/types/onyx/TransactionViolation.ts new file mode 100644 index 000000000000..fa7f212aa794 --- /dev/null +++ b/src/types/onyx/TransactionViolation.ts @@ -0,0 +1,36 @@ +/** + * @module TransactionViolation + * @description Transaction Violation + */ + +/** + * Names of the various Transaction Violation types + */ +type ViolationName = + | 'perDayLimit' + | 'maxAge' + | 'overLimit' + | 'overLimitAttendee' + | 'overCategoryLimit' + | 'receiptRequired' + | 'missingCategory' + | 'categoryOutOfPolicy' + | 'missingTag' + | 'tagOutOfPolicy' + | 'missingComment' + | 'taxRequired' + | 'taxOutOfPolicy' + | 'billableExpense'; + +type ViolationType = string; + +type TransactionViolation = { + type: ViolationType; + name: ViolationName; + userMessage: string; + data?: Record; +}; + +type TransactionViolations = Record; + +export type {TransactionViolation, TransactionViolations, ViolationName, ViolationType}; From 58498036860ed27e1e2848dc6213ade02d5d7777 Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Tue, 28 Nov 2023 21:35:03 -0500 Subject: [PATCH 011/161] Fixed up the Onyx Types for Violations & I think I got the functions working --- src/ONYXKEYS.ts | 1 + src/libs/ReportUtils.ts | 72 ++++++++++++-------------- src/types/onyx/OriginalMessage.ts | 2 +- src/types/onyx/TransactionViolation.ts | 57 +++++++++++++------- src/types/onyx/index.ts | 2 + 5 files changed, 75 insertions(+), 59 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index cf98433da76d..62067a864ed5 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -451,6 +451,7 @@ type OnyxValues = { [ONYXKEYS.COLLECTION.TRANSACTION]: OnyxTypes.Transaction; [ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS]: OnyxTypes.RecentlyUsedTags; [ONYXKEYS.COLLECTION.SELECTED_TAB]: string; + [ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS]: OnyxTypes.TransactionViolation[]; // Forms [ONYXKEYS.FORMS.ADD_DEBIT_CARD_FORM]: OnyxTypes.AddDebitCardForm; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 7cd21e19ab0f..af57a2969b5b 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -16,12 +16,11 @@ import CONST from '@src/CONST'; import {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import {Beta, Login, PersonalDetails, Policy, PolicyTags, Report, ReportAction, Transaction} from '@src/types/onyx'; +import {Beta, Login, PersonalDetails, Policy, PolicyTags, Report, ReportAction, Transaction, TransactionViolation} from '@src/types/onyx'; import {Errors, Icon, PendingAction} from '@src/types/onyx/OnyxCommon'; -import {ChangeLog, IOUMessage, OriginalMessageActionName} from '@src/types/onyx/OriginalMessage'; +import OriginalMessage, {ChangeLog, IOUMessage, OriginalMessageActionName, OriginalMessageIOU} from '@src/types/onyx/OriginalMessage'; import {Message, ReportActions} from '@src/types/onyx/ReportAction'; import {Receipt, WaypointCollection} from '@src/types/onyx/Transaction'; -import {TransactionViolation, TransactionViolations} from '@src/types/onyx/TransactionViolation'; import DeepValueOf from '@src/types/utils/DeepValueOf'; import {EmptyObject, isEmptyObject, isNotEmptyObject} from '@src/types/utils/EmptyObject'; import * as CollectionUtils from './CollectionUtils'; @@ -403,11 +402,16 @@ Onyx.connect({ callback: (value) => (loginList = value), }); -const transactionViolations: OnyxCollection = {}; +const transactionViolations: OnyxCollection = {}; Onyx.connect({ key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, - callback: (violations: OnyxCollection, key) => { - if (!key || !violations) { + callback: (violations, key) => { + if (!key) { + return; + } + + if (!violations) { + delete transactionViolations[key]; return; } @@ -3396,43 +3400,36 @@ function shouldHideReport(report: OnyxEntry, currentReportId: string): b return parentReport?.reportID !== report?.reportID && !isChildReportHasComment; } -/** - * @param {String} transactionID - * @returns {Boolean} - */ - -function transactionHasViolation(transactionID) { - const violations = lodashGet(transactionViolations, transactionID, []); - return _.some(violations, (violation) => violation.type === 'violation'); +function transactionHasViolation(transactionID: string): boolean { + const violations = transactionViolations ? transactionViolations?.[transactionID] : []; + if (!violations) { + return false; + } + return violations.some((violation: TransactionViolation) => violation.type === 'violation'); } -/** - * - * @param {Object} report - * @param {Array | null} betas - * @returns {Boolean} - */ +function isOriginalMessageIOU(message: OriginalMessage): message is OriginalMessageIOU { + return (message as OriginalMessageIOU).actionName === CONST.REPORT.ACTIONS.TYPE.IOU; +} -function transactionThreadHasViolations(report, betas) { - if (!Permissions.canUseViolations(betas)) { +function transactionThreadHasViolations(report: Report, betas: Beta[]): boolean { + if (!Permissions.canUseViolations(betas) || !reportActions) { return false; } if (!report.parentReportActionID) { return false; } - - const parentReportAction = lodashGet(reportActions, `${report.parentReportID}.${report.parentReportActionID}`); + const parentReportAction = reportActions[`${report.parentReportID}`]?.[`${report.parentReportActionID}`]; if (!parentReportAction) { return false; } - // eslint-disable-next-line es/no-nullish-coalescing-operators - const transactionID = parentReportAction.originalMessage.IOUTransactionID ?? 0; - if (!transactionID) { - return false; - } - // eslint-disable-next-line es/no-nullish-coalescing-operators - const reportID = parentReportAction.originalMessage.IOUReportID ?? 0; - if (!reportID) { + const transactionID = isOriginalMessageIOU(parentReportAction?.originalMessage as OriginalMessage) + ? (parentReportAction.originalMessage as OriginalMessageIOU).originalMessage.IOUTransactionID + : ''; + const reportID = isOriginalMessageIOU(parentReportAction?.originalMessage as OriginalMessage) + ? (parentReportAction.originalMessage as OriginalMessageIOU).originalMessage.IOUReportID + : ''; + if (!transactionID || !reportID) { return false; } if (!isCurrentUserSubmitter(reportID)) { @@ -3441,14 +3438,9 @@ function transactionThreadHasViolations(report, betas) { return transactionHasViolation(transactionID); } -/** - * @param {String} reportID - * @returns {Boolean} - */ - -function reportHasViolations(reportID) { +function reportHasViolations(reportID: string): boolean { const transactions = TransactionUtils.getAllReportTransactions(reportID); - return _.some(transactions, (transaction) => transactionHasViolation(transaction.transactionID)); + return transactions.some((transaction) => transactionHasViolation(transaction.transactionID)); } /** @@ -3524,7 +3516,7 @@ function shouldReportBeInOptionList( } // Always show IOU reports with violations - if (isExpenseRequest(report) && transactionThreadHasViolations(report)) { + if (isExpenseRequest(report) && transactionThreadHasViolations(report, betas)) { return true; } diff --git a/src/types/onyx/OriginalMessage.ts b/src/types/onyx/OriginalMessage.ts index c5d9c27d34a1..6e12a7d286e7 100644 --- a/src/types/onyx/OriginalMessage.ts +++ b/src/types/onyx/OriginalMessage.ts @@ -223,4 +223,4 @@ type OriginalMessage = | OriginalMessageMoved; export default OriginalMessage; -export type {ChronosOOOEvent, Decision, Reaction, ActionName, IOUMessage, Closed, OriginalMessageActionName, ChangeLog}; +export type {ChronosOOOEvent, Decision, Reaction, ActionName, IOUMessage, Closed, OriginalMessageActionName, OriginalMessageIOU, ChangeLog}; diff --git a/src/types/onyx/TransactionViolation.ts b/src/types/onyx/TransactionViolation.ts index fa7f212aa794..02db3921c573 100644 --- a/src/types/onyx/TransactionViolation.ts +++ b/src/types/onyx/TransactionViolation.ts @@ -2,25 +2,36 @@ * @module TransactionViolation * @description Transaction Violation */ +import PropTypes from 'prop-types'; /** - * Names of the various Transaction Violation types + * Names of the various Transaction Violation types. + * Defined as an array so it can be used in `PropTypes.oneOf` */ -type ViolationName = - | 'perDayLimit' - | 'maxAge' - | 'overLimit' - | 'overLimitAttendee' - | 'overCategoryLimit' - | 'receiptRequired' - | 'missingCategory' - | 'categoryOutOfPolicy' - | 'missingTag' - | 'tagOutOfPolicy' - | 'missingComment' - | 'taxRequired' - | 'taxOutOfPolicy' - | 'billableExpense'; +const violationNames = [ + 'perDayLimit', + 'maxAge', + 'overLimit', + 'overLimitAttendee', + 'overCategoryLimit', + 'receiptRequired', + 'missingCategory', + 'categoryOutOfPolicy', + 'missingTag', + 'tagOutOfPolicy', + 'missingComment', + 'taxRequired', + 'taxOutOfPolicy', + 'billableExpense', +] as const; + +/** + * Names of the various Transaction Violation types. + * + * The list is first defined as an array so it can be used in `PropTypes.oneOf`, and + * converted to a union type here for use in typescript. + */ +type ViolationName = (typeof violationNames)[number]; type ViolationType = string; @@ -31,6 +42,16 @@ type TransactionViolation = { data?: Record; }; -type TransactionViolations = Record; +const transactionViolationPropType = PropTypes.shape({ + type: PropTypes.string.isRequired, + name: PropTypes.oneOf(violationNames).isRequired, + userMessage: PropTypes.string.isRequired, + data: PropTypes.objectOf(PropTypes.string), +}); + +const transactionViolationsPropTypes = PropTypes.arrayOf(transactionViolationPropType); + +export default TransactionViolation; +export {transactionViolationPropType, transactionViolationsPropTypes}; -export type {TransactionViolation, TransactionViolations, ViolationName, ViolationType}; +export type {ViolationName, ViolationType}; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index e7b9c7661c79..ad1e1c17d6ca 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -42,6 +42,7 @@ import SecurityGroup from './SecurityGroup'; import Session from './Session'; import Task from './Task'; import Transaction from './Transaction'; +import TransactionViolation from './TransactionViolation'; import User from './User'; import UserLocation from './UserLocation'; import UserWallet from './UserWallet'; @@ -103,6 +104,7 @@ export type { Session, Task, Transaction, + TransactionViolation, User, UserWallet, WalletAdditionalDetails, From f7fa4bf321fd2851d37e5c35a26c5a6149b637bf Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Wed, 29 Nov 2023 09:30:35 -0500 Subject: [PATCH 012/161] Finally got the tests up and working correctly --- src/components/LHNOptionsList/LHNOptionsList.js | 3 ++- tests/unit/SidebarFilterTest.js | 6 ------ 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.js b/src/components/LHNOptionsList/LHNOptionsList.js index 605ad761240a..4a2d13d716c4 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.js +++ b/src/components/LHNOptionsList/LHNOptionsList.js @@ -80,7 +80,7 @@ const defaultProps = { personalDetails: {}, transactions: {}, draftComments: {}, - betas: {}, + betas: [], ...withCurrentReportIDDefaultProps, }; @@ -194,6 +194,7 @@ export default compose( }, betas: { key: ONYXKEYS.BETAS, + initialValue: [], }, }), )(LHNOptionsList); diff --git a/tests/unit/SidebarFilterTest.js b/tests/unit/SidebarFilterTest.js index f54b2a6f5c95..362990e113ad 100644 --- a/tests/unit/SidebarFilterTest.js +++ b/tests/unit/SidebarFilterTest.js @@ -111,7 +111,6 @@ describe('Sidebar', () => { // When Onyx is updated to contain that report .then(() => Onyx.multiSet({ - [ONYXKEYS.BETAS]: [CONST.BETAS.VIOLATIONS], [`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`]: report, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_REPORT_DATA]: false, @@ -142,7 +141,6 @@ describe('Sidebar', () => { // When Onyx is updated to contain that data and the sidebar re-renders .then(() => Onyx.multiSet({ - [ONYXKEYS.BETAS]: [CONST.BETAS.VIOLATIONS], [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_REPORT_DATA]: false, [`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`]: report, @@ -195,7 +193,6 @@ describe('Sidebar', () => { // When Onyx is updated to contain that data and the sidebar re-renders .then(() => Onyx.multiSet({ - [ONYXKEYS.BETAS]: [CONST.BETAS.VIOLATIONS], [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_REPORT_DATA]: false, [`${ONYXKEYS.COLLECTION.REPORT}${report1.reportID}`]: report1, @@ -247,7 +244,6 @@ describe('Sidebar', () => { // When Onyx is updated to contain that data and the sidebar re-renders .then(() => Onyx.multiSet({ - [ONYXKEYS.BETAS]: [CONST.BETAS.VIOLATIONS], [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_REPORT_DATA]: false, [`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`]: report, @@ -380,7 +376,6 @@ describe('Sidebar', () => { // When Onyx is updated to contain that data and the sidebar re-renders .then(() => Onyx.multiSet({ - [ONYXKEYS.BETAS]: [CONST.BETAS.VIOLATIONS], [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.GSD, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_REPORT_DATA]: false, @@ -452,7 +447,6 @@ describe('Sidebar', () => { // When Onyx is updated to contain that data and the sidebar re-renders .then(() => Onyx.multiSet({ - [ONYXKEYS.BETAS]: [CONST.BETAS.VIOLATIONS], [ONYXKEYS.NVP_PRIORITY_MODE]: CONST.PRIORITY_MODE.GSD, [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_REPORT_DATA]: false, From 02fa71460eaddce5f1c10d543c4702005da7963d Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Wed, 29 Nov 2023 10:31:23 -0500 Subject: [PATCH 013/161] Fixed types and property checks for violations --- src/libs/ReportUtils.ts | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index af57a2969b5b..d06a8baa2dae 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3401,17 +3401,13 @@ function shouldHideReport(report: OnyxEntry, currentReportId: string): b } function transactionHasViolation(transactionID: string): boolean { - const violations = transactionViolations ? transactionViolations?.[transactionID] : []; + const violations = transactionViolations ? transactionViolations[transactionID]?.values : []; if (!violations) { return false; } return violations.some((violation: TransactionViolation) => violation.type === 'violation'); } -function isOriginalMessageIOU(message: OriginalMessage): message is OriginalMessageIOU { - return (message as OriginalMessageIOU).actionName === CONST.REPORT.ACTIONS.TYPE.IOU; -} - function transactionThreadHasViolations(report: Report, betas: Beta[]): boolean { if (!Permissions.canUseViolations(betas) || !reportActions) { return false; @@ -3423,12 +3419,11 @@ function transactionThreadHasViolations(report: Report, betas: Beta[]): boolean if (!parentReportAction) { return false; } - const transactionID = isOriginalMessageIOU(parentReportAction?.originalMessage as OriginalMessage) - ? (parentReportAction.originalMessage as OriginalMessageIOU).originalMessage.IOUTransactionID - : ''; - const reportID = isOriginalMessageIOU(parentReportAction?.originalMessage as OriginalMessage) - ? (parentReportAction.originalMessage as OriginalMessageIOU).originalMessage.IOUReportID - : ''; + if (parentReportAction.actionName !== CONST.REPORT.ACTIONS.TYPE.IOU) { + return false; + } + const transactionID = parentReportAction?.originalMessage?.IOUTransactionID; + const reportID = parentReportAction?.originalMessage?.IOUReportID; if (!transactionID || !reportID) { return false; } From 63ab54424aa8be7377a1f6e8c8ad0fc093659a5a Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Thu, 30 Nov 2023 10:36:15 -0500 Subject: [PATCH 014/161] Added some doc comments and also fixed the violation type --- src/ONYXKEYS.ts | 2 +- src/libs/ReportUtils.ts | 24 ++++++-- src/types/onyx/TransactionViolation.ts | 84 ++++++++++++-------------- src/types/onyx/index.ts | 3 +- 4 files changed, 61 insertions(+), 52 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 62067a864ed5..fc6d2927a1b1 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -451,7 +451,7 @@ type OnyxValues = { [ONYXKEYS.COLLECTION.TRANSACTION]: OnyxTypes.Transaction; [ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS]: OnyxTypes.RecentlyUsedTags; [ONYXKEYS.COLLECTION.SELECTED_TAB]: string; - [ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS]: OnyxTypes.TransactionViolation[]; + [ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS]: OnyxTypes.TransactionViolations; // Forms [ONYXKEYS.FORMS.ADD_DEBIT_CARD_FORM]: OnyxTypes.AddDebitCardForm; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index d06a8baa2dae..e250d8ad83ff 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -16,9 +16,9 @@ import CONST from '@src/CONST'; import {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import {Beta, Login, PersonalDetails, Policy, PolicyTags, Report, ReportAction, Transaction, TransactionViolation} from '@src/types/onyx'; +import {Beta, Login, PersonalDetails, Policy, PolicyTags, Report, ReportAction, Transaction, TransactionViolation, TransactionViolations} from '@src/types/onyx'; import {Errors, Icon, PendingAction} from '@src/types/onyx/OnyxCommon'; -import OriginalMessage, {ChangeLog, IOUMessage, OriginalMessageActionName, OriginalMessageIOU} from '@src/types/onyx/OriginalMessage'; +import {ChangeLog, IOUMessage, OriginalMessageActionName} from '@src/types/onyx/OriginalMessage'; import {Message, ReportActions} from '@src/types/onyx/ReportAction'; import {Receipt, WaypointCollection} from '@src/types/onyx/Transaction'; import DeepValueOf from '@src/types/utils/DeepValueOf'; @@ -402,7 +402,7 @@ Onyx.connect({ callback: (value) => (loginList = value), }); -const transactionViolations: OnyxCollection = {}; +const transactionViolations: OnyxCollection = {}; Onyx.connect({ key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, callback: (violations, key) => { @@ -3400,14 +3400,24 @@ function shouldHideReport(report: OnyxEntry, currentReportId: string): b return parentReport?.reportID !== report?.reportID && !isChildReportHasComment; } +/** + * + * Check if there are any violations belonging to the transaction in the transactionsViolations Onyx object + * then checks that the violation is of the proper type + */ function transactionHasViolation(transactionID: string): boolean { - const violations = transactionViolations ? transactionViolations[transactionID]?.values : []; + const violations = transactionViolations ? transactionViolations[transactionID]?.value : []; if (!violations) { return false; } return violations.some((violation: TransactionViolation) => violation.type === 'violation'); } +/** + * Checks to see if a report's parentAction is a money request that contains a violation + * This only pertains to report's that a user submitted and if it is open or processing + */ + function transactionThreadHasViolations(report: Report, betas: Beta[]): boolean { if (!Permissions.canUseViolations(betas) || !reportActions) { return false; @@ -3430,9 +3440,15 @@ function transactionThreadHasViolations(report: Report, betas: Beta[]): boolean if (!isCurrentUserSubmitter(reportID)) { return false; } + if (report?.stateNum !== CONST.REPORT.STATE_NUM.OPEN && report?.stateNum !== CONST.REPORT.STATE_NUM.PROCESSING) { + return false; + } return transactionHasViolation(transactionID); } +/** + * Checks to see if a report contains a violation + */ function reportHasViolations(reportID: string): boolean { const transactions = TransactionUtils.getAllReportTransactions(reportID); return transactions.some((transaction) => transactionHasViolation(transaction.transactionID)); diff --git a/src/types/onyx/TransactionViolation.ts b/src/types/onyx/TransactionViolation.ts index 02db3921c573..1bb1f179f601 100644 --- a/src/types/onyx/TransactionViolation.ts +++ b/src/types/onyx/TransactionViolation.ts @@ -1,57 +1,49 @@ /** - * @module TransactionViolation - * @description Transaction Violation + * Names of transaction violations */ -import PropTypes from 'prop-types'; -/** - * Names of the various Transaction Violation types. - * Defined as an array so it can be used in `PropTypes.oneOf` - */ -const violationNames = [ - 'perDayLimit', - 'maxAge', - 'overLimit', - 'overLimitAttendee', - 'overCategoryLimit', - 'receiptRequired', - 'missingCategory', - 'categoryOutOfPolicy', - 'missingTag', - 'tagOutOfPolicy', - 'missingComment', - 'taxRequired', - 'taxOutOfPolicy', - 'billableExpense', -] as const; - -/** - * Names of the various Transaction Violation types. - * - * The list is first defined as an array so it can be used in `PropTypes.oneOf`, and - * converted to a union type here for use in typescript. - */ -type ViolationName = (typeof violationNames)[number]; - -type ViolationType = string; +type ViolationName = + | 'allTagLevelsRequired' + | 'autoReportedRejectedExpense' + | 'billableExpense' + | 'cashExpenseWithNoReceipt' + | 'categoryOutOfPolicy' + | 'conversionSurcharge' + | 'customUnitOutOfPolicy' + | 'duplicatedTransaction' + | 'fieldRequired' + | 'futureDate' + | 'invoiceMarkup' + | 'maxAge' + | 'missingCategory' + | 'missingComment' + | 'missingTag' + | 'modifiedAmount' + | 'modifiedDate' + | 'nonExpensiworksExpense' + | 'overAutoApprovalLimit' + | 'overCategoryLimit' + | 'overLimit' + | 'overLimitAttendee' + | 'perDayLimit' + | 'receiptNotSmartScanned' + | 'receiptRequired' + | 'rter' + | 'smartscanFailed' + | 'someTagLevelsRequired' + | 'tagOutOfPolicy' + | 'taxAmountChanged' + | 'taxOutOfPolicy' + | 'taxRateChanged' + | 'taxRequired'; type TransactionViolation = { - type: ViolationType; + type: string; name: ViolationName; userMessage: string; data?: Record; }; -const transactionViolationPropType = PropTypes.shape({ - type: PropTypes.string.isRequired, - name: PropTypes.oneOf(violationNames).isRequired, - userMessage: PropTypes.string.isRequired, - data: PropTypes.objectOf(PropTypes.string), -}); - -const transactionViolationsPropTypes = PropTypes.arrayOf(transactionViolationPropType); - -export default TransactionViolation; -export {transactionViolationPropType, transactionViolationsPropTypes}; +type TransactionViolations = Record; -export type {ViolationName, ViolationType}; +export type {TransactionViolation, TransactionViolations, ViolationName}; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index ad1e1c17d6ca..9e81f2025255 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -42,7 +42,7 @@ import SecurityGroup from './SecurityGroup'; import Session from './Session'; import Task from './Task'; import Transaction from './Transaction'; -import TransactionViolation from './TransactionViolation'; +import {TransactionViolation, TransactionViolations} from './TransactionViolation'; import User from './User'; import UserLocation from './UserLocation'; import UserWallet from './UserWallet'; @@ -105,6 +105,7 @@ export type { Task, Transaction, TransactionViolation, + TransactionViolations, User, UserWallet, WalletAdditionalDetails, From c69c1df69a7f79a306af37339e07b248dda663ad Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Thu, 30 Nov 2023 11:13:28 -0500 Subject: [PATCH 015/161] Fixed perf test --- tests/perf-test/SidebarUtils.perf-test.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/perf-test/SidebarUtils.perf-test.ts b/tests/perf-test/SidebarUtils.perf-test.ts index 7f9957232cfb..fb3cac24053a 100644 --- a/tests/perf-test/SidebarUtils.perf-test.ts +++ b/tests/perf-test/SidebarUtils.perf-test.ts @@ -3,7 +3,7 @@ import {measureFunction} from 'reassure'; import SidebarUtils from '@libs/SidebarUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import {PersonalDetails} from '@src/types/onyx'; +import {Beta, PersonalDetails} from '@src/types/onyx'; import Policy from '@src/types/onyx/Policy'; import Report from '@src/types/onyx/Report'; import ReportAction, {ReportActions} from '@src/types/onyx/ReportAction'; @@ -51,13 +51,14 @@ test('getOptionData on 5k reports', async () => { const preferredLocale = 'en'; const policy = createRandomPolicy(1); const parentReportAction = createRandomReportAction(1); + const betas: Beta[] = []; Onyx.multiSet({ ...mockedResponseMap, }); await waitForBatchedUpdates(); - await measureFunction(() => SidebarUtils.getOptionData(report, reportActions, personalDetails, preferredLocale, policy, parentReportAction), {runs}); + await measureFunction(() => SidebarUtils.getOptionData(report, reportActions, personalDetails, preferredLocale, policy, parentReportAction, betas), {runs}); }); test('getOrderedReportIDs on 5k reports', async () => { From 5e122efec4221d246d3c9ba87f34bd666454aa07 Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Thu, 30 Nov 2023 13:07:34 -0500 Subject: [PATCH 016/161] Removed unnecessary export --- src/types/onyx/OriginalMessage.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/types/onyx/OriginalMessage.ts b/src/types/onyx/OriginalMessage.ts index 6c86a6b1be69..0dc532ebeded 100644 --- a/src/types/onyx/OriginalMessage.ts +++ b/src/types/onyx/OriginalMessage.ts @@ -225,4 +225,4 @@ type OriginalMessage = | OriginalMessageMoved; export default OriginalMessage; -export type {ChronosOOOEvent, Decision, Reaction, ActionName, IOUMessage, Closed, OriginalMessageActionName, OriginalMessageIOU, ChangeLog}; +export type {ChronosOOOEvent, Decision, Reaction, ActionName, IOUMessage, Closed, OriginalMessageActionName, ChangeLog}; From e53902e3f60cd1f191e85ef85d8c7175d2fc54c0 Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Thu, 30 Nov 2023 13:09:29 -0500 Subject: [PATCH 017/161] Restore betas in test --- tests/unit/SidebarFilterTest.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/unit/SidebarFilterTest.js b/tests/unit/SidebarFilterTest.js index 362990e113ad..23a958e3aa9d 100644 --- a/tests/unit/SidebarFilterTest.js +++ b/tests/unit/SidebarFilterTest.js @@ -141,6 +141,7 @@ describe('Sidebar', () => { // When Onyx is updated to contain that data and the sidebar re-renders .then(() => Onyx.multiSet({ + [ONYXKEYS.BETAS]: [], [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_REPORT_DATA]: false, [`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`]: report, @@ -193,6 +194,7 @@ describe('Sidebar', () => { // When Onyx is updated to contain that data and the sidebar re-renders .then(() => Onyx.multiSet({ + [ONYXKEYS.BETAS]: [], [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_REPORT_DATA]: false, [`${ONYXKEYS.COLLECTION.REPORT}${report1.reportID}`]: report1, @@ -244,6 +246,7 @@ describe('Sidebar', () => { // When Onyx is updated to contain that data and the sidebar re-renders .then(() => Onyx.multiSet({ + [ONYXKEYS.BETAS]: [], [ONYXKEYS.PERSONAL_DETAILS_LIST]: LHNTestUtils.fakePersonalDetails, [ONYXKEYS.IS_LOADING_REPORT_DATA]: false, [`${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`]: report, From c49b03f103b401ddddf3b04b9912f152f84d7426 Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Thu, 30 Nov 2023 13:25:11 -0500 Subject: [PATCH 018/161] Switch to destructuring --- src/libs/ReportUtils.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 1412cfa73067..809a47195a36 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3448,18 +3448,17 @@ function transactionThreadHasViolations(report: Report, betas: Beta[]): boolean if (parentReportAction.actionName !== CONST.REPORT.ACTIONS.TYPE.IOU) { return false; } - const transactionID = parentReportAction?.originalMessage?.IOUTransactionID; - const reportID = parentReportAction?.originalMessage?.IOUReportID; - if (!transactionID || !reportID) { + const {IOUTransactionID, IOUReportID} = parentReportAction?.originalMessage; + if (!IOUTransactionID || !IOUReportID) { return false; } - if (!isCurrentUserSubmitter(reportID)) { + if (!isCurrentUserSubmitter(IOUReportID)) { return false; } if (report?.stateNum !== CONST.REPORT.STATE_NUM.OPEN && report?.stateNum !== CONST.REPORT.STATE_NUM.PROCESSING) { return false; } - return transactionHasViolation(transactionID); + return transactionHasViolation(IOUTransactionID); } /** From a28efcac58ad7dfd35c24585b3df85187529e786 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Fri, 1 Dec 2023 16:29:28 +0100 Subject: [PATCH 019/161] fix: types in Task lib --- src/libs/ReportActionsUtils.ts | 2 +- src/libs/ReportUtils.ts | 8 +- src/libs/actions/Task.ts | 305 +++++++++++++++++++-------------- src/types/onyx/Task.ts | 3 + 4 files changed, 185 insertions(+), 133 deletions(-) diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index bd475a57954e..0138982c6256 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -122,7 +122,7 @@ function isThreadParentMessage(reportAction: OnyxEntry, reportID: * * @deprecated Use Onyx.connect() or withOnyx() instead */ -function getParentReportAction(report: OnyxEntry, allReportActionsParam?: OnyxCollection): ReportAction | Record { +function getParentReportAction(report: OnyxEntry | EmptyObject, allReportActionsParam?: OnyxCollection): ReportAction | Record { if (!report?.parentReportID || !report.parentReportActionID) { return {}; } diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 599963b6a9aa..6b3fcf8a3314 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -187,7 +187,7 @@ type OptimisticClosedReportAction = Pick< type OptimisticCreatedReportAction = Pick< ReportAction, - 'actionName' | 'actorAccountID' | 'automatic' | 'avatar' | 'created' | 'message' | 'person' | 'reportActionID' | 'shouldShow' | 'pendingAction' + 'actionName' | 'actorAccountID' | 'automatic' | 'avatar' | 'created' | 'message' | 'person' | 'reportActionID' | 'shouldShow' | 'pendingAction' | 'originalMessage' >; type OptimisticChatReport = Pick< @@ -270,6 +270,7 @@ type OptimisticTaskReport = Pick< | 'stateNum' | 'statusNum' | 'notificationPreference' + | 'parentReportActionID' >; type TransactionDetails = @@ -841,7 +842,7 @@ function isArchivedRoom(report: OnyxEntry | EmptyObject): boolean { /** * Checks if the current user is allowed to comment on the given report. */ -function isAllowedToComment(report: Report): boolean { +function isAllowedToComment(report: OnyxEntry | EmptyObject): boolean { // Default to allowing all users to post const capability = report?.writeCapability ?? CONST.REPORT.WRITE_CAPABILITIES.ALL; @@ -2150,7 +2151,7 @@ function getModifiedExpenseOriginalMessage(oldTransaction: OnyxEntry): OnyxEntry | EmptyObject { +function getParentReport(report: OnyxEntry | EmptyObject): OnyxEntry | EmptyObject { if (!report?.parentReportID) { return {}; } @@ -3078,6 +3079,7 @@ function buildOptimisticCreatedReportAction(emailCreatingAction: string, created actionName: CONST.REPORT.ACTIONS.TYPE.CREATED, pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, actorAccountID: currentUserAccountID, + originalMessage: undefined, message: [ { type: CONST.REPORT.MESSAGE.TYPE.TEXT, diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index 03f4b30d56f6..50f6434e4666 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -1,4 +1,4 @@ -import Onyx from 'react-native-onyx'; +import Onyx, {OnyxEntry, OnyxUpdate} from 'react-native-onyx'; import * as Expensicons from '@components/Icon/Expensicons'; import * as API from '@libs/API'; import DateUtils from '@libs/DateUtils'; @@ -13,10 +13,16 @@ import * as UserUtils from '@libs/UserUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import {PersonalDetails, Report, Task} from '@src/types/onyx'; +import * as OnyxTypes from '@src/types/onyx'; +import {Icon} from '@src/types/onyx/OnyxCommon'; +import * as Report from './Report'; + +type OptimisticReport = Pick; +type Assignee = {icons: Icon[]; displayName: string; subtitle: string}; + +let currentUserEmail: string | undefined; +let currentUserAccountID: number | undefined; -let currentUserEmail: string; -let currentUserAccountID: number; Onyx.connect({ key: ONYXKEYS.SESSION, callback: (value) => { @@ -25,7 +31,7 @@ Onyx.connect({ }, }); -let allPersonalDetails: Record | null; +let allPersonalDetails: Record | null; Onyx.connect({ key: ONYXKEYS.PERSONAL_DETAILS_LIST, callback: (value) => (allPersonalDetails = value), @@ -59,22 +65,22 @@ function createTaskAndNavigate( description: string, assigneeEmail: string, assigneeAccountID = 0, - assigneeChatReport: Report | null = null, + assigneeChatReport: OnyxTypes.Report | null = null, policyID = CONST.POLICY.OWNER_EMAIL_FAKE, ) { - const optimisticTaskReport = ReportUtils.buildOptimisticTaskReport(currentUserAccountID, assigneeAccountID, parentReportID, title, description, policyID); + const optimisticTaskReport = ReportUtils.buildOptimisticTaskReport(currentUserAccountID ?? 0, assigneeAccountID, parentReportID, title, description, policyID); - const assigneeChatReportID = assigneeChatReport ? assigneeChatReport.reportID : 0; + const assigneeChatReportID = assigneeChatReport ? assigneeChatReport.reportID : ''; const taskReportID = optimisticTaskReport.reportID; let assigneeChatReportOnyxData; // Parent ReportAction indicating that a task has been created - const optimisticTaskCreatedAction = ReportUtils.buildOptimisticCreatedReportAction(currentUserEmail); + const optimisticTaskCreatedAction = ReportUtils.buildOptimisticCreatedReportAction(currentUserEmail ?? ''); const optimisticAddCommentReport = ReportUtils.buildOptimisticTaskCommentReportAction(taskReportID, title, assigneeAccountID, `task for ${title}`, parentReportID); optimisticTaskReport.parentReportActionID = optimisticAddCommentReport.reportAction.reportActionID; const currentTime = DateUtils.getDBTime(); - const lastCommentText = ReportUtils.formatReportLastMessageText(optimisticAddCommentReport.reportAction.message[0].text); + const lastCommentText = ReportUtils.formatReportLastMessageText(optimisticAddCommentReport?.reportAction?.message?.[0].text ?? ''); const optimisticParentReport = { lastVisibleActionCreated: currentTime, lastMessageText: lastCommentText, @@ -86,7 +92,7 @@ function createTaskAndNavigate( // We're only setting onyx data for the task report here because it's possible for the parent report to not exist yet (if you're assigning a task to someone you haven't chatted with before) // So we don't want to set the parent report data until we've successfully created that chat report // FOR TASK REPORT - const optimisticData = [ + const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.SET, key: `${ONYXKEYS.COLLECTION.REPORT}${optimisticTaskReport.reportID}`, @@ -104,12 +110,12 @@ function createTaskAndNavigate( { onyxMethod: Onyx.METHOD.SET, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${optimisticTaskReport.reportID}`, - value: {[optimisticTaskCreatedAction.reportActionID]: optimisticTaskCreatedAction}, + value: {[optimisticTaskCreatedAction.reportActionID ?? '']: optimisticTaskCreatedAction}, }, ]; // FOR TASK REPORT - const successData = [ + const successData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${optimisticTaskReport.reportID}`, @@ -131,7 +137,7 @@ function createTaskAndNavigate( ]; // FOR TASK REPORT - const failureData = [ + const failureData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${optimisticTaskReport.reportID}`, @@ -150,7 +156,7 @@ function createTaskAndNavigate( if (assigneeChatReport) { assigneeChatReportOnyxData = ReportUtils.getTaskAssigneeChatOnyxData( - currentUserAccountID, + currentUserAccountID ?? 0, assigneeAccountID, taskReportID, assigneeChatReportID, @@ -158,6 +164,7 @@ function createTaskAndNavigate( title, assigneeChatReport, ); + optimisticData.push(...assigneeChatReportOnyxData.optimisticData); successData.push(...assigneeChatReportOnyxData.successData); failureData.push(...assigneeChatReportOnyxData.failureData); @@ -174,7 +181,7 @@ function createTaskAndNavigate( { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`, - value: {[optimisticAddCommentReport.reportAction.reportActionID]: optimisticAddCommentReport.reportAction}, + value: {[optimisticAddCommentReport.reportAction.reportActionID ?? '']: optimisticAddCommentReport.reportAction}, }, ); @@ -182,7 +189,7 @@ function createTaskAndNavigate( successData.push({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`, - value: {[optimisticAddCommentReport.reportAction.reportActionID]: {pendingAction: null}}, + value: {[optimisticAddCommentReport.reportAction.reportActionID ?? '']: {pendingAction: null}}, }); // FOR PARENT REPORT (SHARE DESTINATION) @@ -190,7 +197,7 @@ function createTaskAndNavigate( onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`, value: { - [optimisticAddCommentReport.reportAction.reportActionID]: { + [optimisticAddCommentReport.reportAction.reportActionID ?? '']: { errors: ErrorUtils.getMicroSecondOnyxError('task.genericCreateTaskFailureMessage'), }, }, @@ -198,23 +205,34 @@ function createTaskAndNavigate( clearOutTaskInfo(); - API.write( - 'CreateTask', - { - parentReportActionID: optimisticAddCommentReport.reportAction.reportActionID, - parentReportID, - taskReportID: optimisticTaskReport.reportID, - createdTaskReportActionID: optimisticTaskCreatedAction.reportActionID, - title: optimisticTaskReport.reportName, - description: optimisticTaskReport.description, - assignee: assigneeEmail, - assigneeAccountID, - assigneeChatReportID, - assigneeChatReportActionID: assigneeChatReportOnyxData?.optimisticAssigneeAddComment.reportAction.reportActionID ?? 0, - assigneeChatCreatedReportActionID: assigneeChatReportOnyxData?.optimisticChatCreatedReportAction.reportActionID ?? 0, - }, - {optimisticData, successData, failureData}, - ); + type CreateTaskParameters = { + parentReportActionID?: string; + parentReportID?: string; + taskReportID?: string; + createdTaskReportActionID?: string; + title?: string; + description?: string; + assignee?: string; + assigneeAccountID?: number; + assigneeChatReportID?: string; + assigneeChatReportActionID?: string; + assigneeChatCreatedReportActionID?: string; + }; + const parameters: CreateTaskParameters = { + parentReportActionID: optimisticAddCommentReport.reportAction.reportActionID, + parentReportID, + taskReportID: optimisticTaskReport.reportID, + createdTaskReportActionID: optimisticTaskCreatedAction.reportActionID, + title: optimisticTaskReport.reportName, + description: optimisticTaskReport.description, + assignee: assigneeEmail, + assigneeAccountID, + assigneeChatReportID, + assigneeChatReportActionID: assigneeChatReportOnyxData?.optimisticAssigneeAddComment?.reportAction.reportActionID ?? '', + assigneeChatCreatedReportActionID: assigneeChatReportOnyxData?.optimisticChatCreatedReportAction?.reportActionID ?? '', + }; + + API.write('CreateTask', parameters, {optimisticData, successData, failureData}); Navigation.dismissModal(parentReportID); } @@ -222,12 +240,12 @@ function createTaskAndNavigate( /** * Complete a task */ -function completeTask(taskReport: Report) { +function completeTask(taskReport: OnyxTypes.Report) { const taskReportID = taskReport.reportID; const message = `marked as complete`; const completedTaskReportAction = ReportUtils.buildOptimisticTaskReportAction(taskReportID, CONST.REPORT.ACTIONS.TYPE.TASKCOMPLETED, message); - const optimisticData = [ + const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${taskReportID}`, @@ -244,7 +262,7 @@ function completeTask(taskReport: Report) { }, ]; - const successData = [ + const successData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${taskReportID}`, @@ -256,7 +274,7 @@ function completeTask(taskReport: Report) { }, ]; - const failureData = [ + const failureData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${taskReportID}`, @@ -276,25 +294,28 @@ function completeTask(taskReport: Report) { }, ]; - API.write( - 'CompleteTask', - { - taskReportID, - completedTaskReportActionID: completedTaskReportAction.reportActionID, - }, - {optimisticData, successData, failureData}, - ); + type CompleteTaskParameters = { + taskReportID?: string; + completedTaskReportActionID?: string; + }; + + const parameters: CompleteTaskParameters = { + taskReportID, + completedTaskReportActionID: completedTaskReportAction.reportActionID, + }; + + API.write('CompleteTask', parameters, {optimisticData, successData, failureData}); } /** * Reopen a closed task */ -function reopenTask(taskReport: Report) { +function reopenTask(taskReport: OnyxTypes.Report) { const taskReportID = taskReport.reportID; const message = `marked as incomplete`; const reopenedTaskReportAction = ReportUtils.buildOptimisticTaskReportAction(taskReportID, CONST.REPORT.ACTIONS.TYPE.TASKREOPENED, message); - const optimisticData = [ + const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${taskReportID}`, @@ -314,7 +335,7 @@ function reopenTask(taskReport: Report) { }, ]; - const successData = [ + const successData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${taskReportID}`, @@ -325,7 +346,7 @@ function reopenTask(taskReport: Report) { }, }, ]; - const failureData = [ + const failureData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${taskReportID}`, @@ -345,21 +366,20 @@ function reopenTask(taskReport: Report) { }, ]; - API.write( - 'ReopenTask', - { - taskReportID, - reopenedTaskReportActionID: reopenedTaskReportAction.reportActionID, - }, - {optimisticData, successData, failureData}, - ); + type ReopenTaskParameters = { + taskReportID?: string; + reopenedTaskReportActionID?: string; + }; + + const parameters: ReopenTaskParameters = { + taskReportID, + reopenedTaskReportActionID: reopenedTaskReportAction.reportActionID, + }; + + API.write('ReopenTask', parameters, {optimisticData, successData, failureData}); } -/** - * @param {object} report - * @param {Object} editedTask - */ -function editTask(report: Report, {title, description}: Task) { +function editTask(report: OnyxTypes.Report, {title, description}: OnyxTypes.Task) { // Create the EditedReportAction on the task const editTaskReportAction = ReportUtils.buildOptimisticEditedTaskReportAction(currentUserEmail); @@ -369,7 +389,7 @@ function editTask(report: Report, {title, description}: Task) { // Description can be unset, so we default to an empty string if so const reportDescription = (description ?? report.description ?? '').trim(); - const optimisticData = [ + const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`, @@ -388,7 +408,8 @@ function editTask(report: Report, {title, description}: Task) { }, }, ]; - const successData = [ + + const successData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, @@ -400,7 +421,8 @@ function editTask(report: Report, {title, description}: Task) { }, }, ]; - const failureData = [ + + const failureData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`, @@ -416,37 +438,42 @@ function editTask(report: Report, {title, description}: Task) { }, ]; - API.write( - 'EditTask', - { - taskReportID: report.reportID, - title: reportName, - description: reportDescription, - editedTaskReportActionID: editTaskReportAction.reportActionID, - }, - {optimisticData, successData, failureData}, - ); + type EditTaskParameters = { + taskReportID?: string; + title?: string; + description?: string; + editedTaskReportActionID?: string; + }; + + const parameters: EditTaskParameters = { + taskReportID: report.reportID, + title: reportName, + description: reportDescription, + editedTaskReportActionID: editTaskReportAction.reportActionID, + }; + + API.write('EditTask', parameters, {optimisticData, successData, failureData}); } -function editTaskAssignee(report: Report, ownerAccountID: number, assigneeEmail: string, assigneeAccountID = 0, assigneeChatReport: Report | null = null) { +function editTaskAssignee(report: OnyxTypes.Report, ownerAccountID: number, assigneeEmail: string, assigneeAccountID = 0, assigneeChatReport: OnyxTypes.Report | null = null) { // Create the EditedReportAction on the task - const editTaskReportAction = ReportUtils.buildOptimisticEditedTaskReportAction(currentUserEmail); + const editTaskReportAction = ReportUtils.buildOptimisticEditedTaskReportAction(currentUserEmail ?? string); const reportName = report.reportName?.trim(); let assigneeChatReportOnyxData; - const assigneeChatReportID = assigneeChatReport ? assigneeChatReport.reportID : 0; - const optimisticReport = { + const assigneeChatReportID = assigneeChatReport ? assigneeChatReport.reportID : ''; + const optimisticReport: OptimisticReport = { reportName, managerID: assigneeAccountID || report.managerID, pendingFields: { ...(assigneeAccountID && {managerID: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), }, - notificationPreference: [assigneeAccountID, ownerAccountID].includes(currentUserAccountID) + notificationPreference: [assigneeAccountID, ownerAccountID].includes(currentUserAccountID ?? 0) ? CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS : CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN, }; - const optimisticData = [ + const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`, @@ -458,14 +485,16 @@ function editTaskAssignee(report: Report, ownerAccountID: number, assigneeEmail: value: optimisticReport, }, ]; - const successData = [ + + const successData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, value: {pendingFields: {...(assigneeAccountID && {managerID: null})}}, }, ]; - const failureData = [ + + const failureData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`, @@ -481,45 +510,51 @@ function editTaskAssignee(report: Report, ownerAccountID: number, assigneeEmail: // 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 = lodashGet(report, 'participantAccountIDs', []); + const participants = report?.participantAccountIDs ?? []; if (!participants.includes(assigneeAccountID)) { optimisticReport.participantAccountIDs = [...participants, assigneeAccountID]; } assigneeChatReportOnyxData = ReportUtils.getTaskAssigneeChatOnyxData( - currentUserAccountID, + currentUserAccountID ?? 0, assigneeAccountID, report.reportID, - assigneeChatReportID, - report.parentReportID, - reportName, + assigneeChatReportID ?? '', + report.parentReportID ?? '', + reportName ?? '', assigneeChatReport, ); + optimisticData.push(...assigneeChatReportOnyxData.optimisticData); successData.push(...assigneeChatReportOnyxData.successData); failureData.push(...assigneeChatReportOnyxData.failureData); } - API.write( - 'EditTaskAssignee', - { - taskReportID: report.reportID, - assignee: assigneeEmail, - editedTaskReportActionID: editTaskReportAction.reportActionID, - assigneeChatReportID, - assigneeChatReportActionID: assigneeChatReportOnyxData?.optimisticAssigneeAddComment.reportAction.reportActionID ?? 0, - assigneeChatCreatedReportActionID: assigneeChatReportOnyxData?.optimisticChatCreatedReportAction.reportActionID ?? 0, - }, - {optimisticData, successData, failureData}, - ); + type EditTaskAssigneeParameters = { + taskReportID?: string; + assignee?: string; + editedTaskReportActionID?: string; + assigneeChatReportID?: string; + assigneeChatReportActionID?: string; + assigneeChatCreatedReportActionID?: string; + }; + + const parameters: EditTaskAssigneeParameters = { + taskReportID: report.reportID, + assignee: assigneeEmail, + editedTaskReportActionID: editTaskReportAction.reportActionID, + assigneeChatReportID, + assigneeChatReportActionID: assigneeChatReportOnyxData?.optimisticAssigneeAddComment?.reportAction.reportActionID ?? '', + assigneeChatCreatedReportActionID: assigneeChatReportOnyxData?.optimisticChatCreatedReportAction?.reportActionID ?? '', + }; + + API.write('EditTaskAssignee', parameters, {optimisticData, successData, failureData}); } /** * Sets the report info for the task being viewed - * - * @param {Object} report */ -function setTaskReport(report: Report) { +function setTaskReport(report: OnyxTypes.Report) { Onyx.merge(ONYXKEYS.TASK, {report}); } @@ -555,7 +590,7 @@ function setShareDestinationValue(shareDestination: string) { /* Sets the assigneeChatReport details for the task */ -function setAssigneeChatReport(chatReport: Report) { +function setAssigneeChatReport(chatReport: OnyxTypes.Report) { Onyx.merge(ONYXKEYS.TASK, {assigneeChatReport: chatReport}); } @@ -564,8 +599,8 @@ function setAssigneeChatReport(chatReport: Report) { * If there is no existing chat, it creates an optimistic chat report * It also sets the shareDestination as that chat report if a share destination isn't already set */ -function setAssigneeValue(assigneeEmail: string, assigneeAccountID: number, shareDestination: string, isCurrentUser = false) { - let chatReport: Report | undefined; +function setAssigneeValue(assigneeEmail: string, assigneeAccountID: number, shareDestination: string, isCurrentUser = false): OnyxEntry | undefined { + let chatReport: OnyxEntry | undefined; if (!isCurrentUser) { chatReport = ReportUtils.getChatByParticipants([assigneeAccountID]); @@ -632,8 +667,9 @@ function clearOutTaskInfoAndNavigate(reportID: string) { /** * Get the assignee data */ -function getAssignee(assigneeAccountID: number, personalDetails: Record) { +function getAssignee(assigneeAccountID: number, personalDetails: Record): Assignee { const details = personalDetails[assigneeAccountID]; + if (!details) { return { icons: [], @@ -641,20 +677,21 @@ function getAssignee(assigneeAccountID: number, personalDetails: Record, personalDetails: Record) { +function getShareDestination(reportID: string, reports: Record, personalDetails: Record) { const report = reports[`report_${reportID}`] ?? {}; - const participantAccountIDs = lodashGet(report, 'participantAccountIDs'); + const participantAccountIDs = report.participantAccountIDs ?? []; const isMultipleParticipant = participantAccountIDs?.length > 1; const displayNamesWithTooltips = ReportUtils.getDisplayNamesWithTooltips(OptionsListUtils.getPersonalDetailsForAccountIDs(participantAccountIDs, personalDetails), isMultipleParticipant); @@ -707,7 +744,7 @@ function cancelTask(taskReportID: string, taskTitle: string, originalStateNum: n }, }; - const optimisticData = [ + const optimisticData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${taskReportID}`, @@ -721,10 +758,10 @@ function cancelTask(taskReportID: string, taskTitle: string, originalStateNum: n }, { onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${parentReport.reportID}`, + key: `${ONYXKEYS.COLLECTION.REPORT}${parentReport?.reportID}`, value: { - lastMessageText: ReportActionsUtils.getLastVisibleMessage(parentReport.reportID, optimisticReportActions).lastMessageText, - lastVisibleActionCreated: lodashGet(ReportActionsUtils.getLastVisibleAction(parentReport.reportID, optimisticReportActions), 'created'), + lastMessageText: ReportActionsUtils.getLastVisibleMessage(parentReport?.reportID, optimisticReportActions).lastMessageText, + lastVisibleActionCreated: ReportActionsUtils.getLastVisibleAction(parentReport?.reportID, optimisticReportActions) ?? 'created', }, }, { @@ -736,12 +773,12 @@ function cancelTask(taskReportID: string, taskTitle: string, originalStateNum: n }, { onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReport.reportID}`, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReport?.reportID}`, value: optimisticReportActions, }, ]; - const successData = [ + const successData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${taskReportID}`, @@ -753,7 +790,7 @@ function cancelTask(taskReportID: string, taskTitle: string, originalStateNum: n }, { onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReport.reportID}`, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReport?.reportID}`, value: { [parentReportAction.reportActionID]: { pendingAction: null, @@ -762,7 +799,7 @@ function cancelTask(taskReportID: string, taskTitle: string, originalStateNum: n }, ]; - const failureData = [ + const failureData: OnyxUpdate[] = [ { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${taskReportID}`, @@ -780,7 +817,7 @@ function cancelTask(taskReportID: string, taskTitle: string, originalStateNum: n }, { onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReport.reportID}`, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReport?.reportID}`, value: { [parentReportAction.reportActionID]: { pendingAction: null, @@ -789,7 +826,17 @@ function cancelTask(taskReportID: string, taskTitle: string, originalStateNum: n }, ]; - API.write('CancelTask', {cancelledTaskReportActionID: optimisticReportActionID, taskReportID}, {optimisticData, successData, failureData}); + type CancelTaskParameters = { + cancelledTaskReportActionID?: string; + taskReportID?: string; + }; + + const parameters: CancelTaskParameters = { + cancelledTaskReportActionID: optimisticReportActionID, + taskReportID, + }; + + API.write('CancelTask', parameters, {optimisticData, successData, failureData}); } /** @@ -803,7 +850,7 @@ function dismissModalAndClearOutTaskInfo() { /** * Returns Task assignee accountID */ -function getTaskAssigneeAccountID(taskReport: Report): number | null { +function getTaskAssigneeAccountID(taskReport: OnyxTypes.Report): number | null | undefined { if (!taskReport) { return null; } @@ -819,14 +866,14 @@ function getTaskAssigneeAccountID(taskReport: Report): number | null { /** * Returns Task owner accountID */ -function getTaskOwnerAccountID(taskReport: Report): number | null { +function getTaskOwnerAccountID(taskReport: OnyxTypes.Report): number | null { return taskReport.ownerAccountID ?? null; } /** * Check if you're allowed to modify the task - anyone that has write access to the report can modify the task */ -function canModifyTask(taskReport: Report, sessionAccountID: number): boolean { +function canModifyTask(taskReport: OnyxTypes.Report, sessionAccountID: number): boolean { if (ReportUtils.isCanceledTaskReport(taskReport)) { return false; } @@ -846,9 +893,9 @@ function clearTaskErrors(reportID: string) { const report = ReportUtils.getReport(reportID); // Delete the task preview in the parent report - if (lodashGet(report, 'pendingFields.createChat') === CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD) { + if (report?.pendingFields?.createChat === CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD) { Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.parentReportID}`, { - [report.parentReportActionID]: null, + [report.parentReportActionID ?? 0]: null, }); Report.navigateToConciergeChatAndDeleteReport(reportID); @@ -864,7 +911,7 @@ function clearTaskErrors(reportID: string) { function getTaskReportActionMessage(actionName: string, reportID: string, isCreateTaskAction: boolean): string { const report = ReportUtils.getReport(reportID); if (isCreateTaskAction) { - return `task for ${report.reportName}`; + return `task for ${report?.reportName}`; } let taskStatusText = ''; switch (actionName) { diff --git a/src/types/onyx/Task.ts b/src/types/onyx/Task.ts index 9d5c83ee4a40..8a399ee4652f 100644 --- a/src/types/onyx/Task.ts +++ b/src/types/onyx/Task.ts @@ -22,6 +22,9 @@ type Task = { /** Report id only when a task was created from a report */ parentReportID?: string; + + /** Chat report with assignee of task */ + assigneeChatReport?: Report; }; export default Task; From 8a1a642c709947430c2b427b4a34a3c4e47f849a Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Mon, 4 Dec 2023 14:37:55 +0100 Subject: [PATCH 020/161] fix: added assertions, fix types --- src/libs/actions/Task.ts | 58 ++++++++++++++++++---------------- src/types/onyx/Report.ts | 1 + src/types/onyx/ReportAction.ts | 46 +++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 28 deletions(-) diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index 50f6434e4666..50348fc08573 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -110,7 +110,7 @@ function createTaskAndNavigate( { onyxMethod: Onyx.METHOD.SET, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${optimisticTaskReport.reportID}`, - value: {[optimisticTaskCreatedAction.reportActionID ?? '']: optimisticTaskCreatedAction}, + value: {[optimisticTaskCreatedAction.reportActionID ?? '']: optimisticTaskCreatedAction as OnyxTypes.ReportAction}, }, ]; @@ -258,7 +258,7 @@ function completeTask(taskReport: OnyxTypes.Report) { { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${taskReportID}`, - value: {[completedTaskReportAction.reportActionID]: completedTaskReportAction}, + value: {[completedTaskReportAction.reportActionID]: completedTaskReportAction as OnyxTypes.ReportAction}, }, ]; @@ -331,7 +331,7 @@ function reopenTask(taskReport: OnyxTypes.Report) { { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${taskReportID}`, - value: {[reopenedTaskReportAction.reportActionID]: reopenedTaskReportAction}, + value: {[reopenedTaskReportAction.reportActionID]: reopenedTaskReportAction as OnyxTypes.ReportAction}, }, ]; @@ -381,7 +381,7 @@ function reopenTask(taskReport: OnyxTypes.Report) { function editTask(report: OnyxTypes.Report, {title, description}: OnyxTypes.Task) { // Create the EditedReportAction on the task - const editTaskReportAction = ReportUtils.buildOptimisticEditedTaskReportAction(currentUserEmail); + const editTaskReportAction = ReportUtils.buildOptimisticEditedTaskReportAction(currentUserEmail ?? ''); // Sometimes title or description is undefined, so we need to check for that, and we provide it to multiple functions const reportName = (title ?? report?.reportName).trim(); @@ -393,7 +393,7 @@ function editTask(report: OnyxTypes.Report, {title, description}: OnyxTypes.Task { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`, - value: {[editTaskReportAction.reportActionID]: editTaskReportAction}, + value: {[editTaskReportAction.reportActionID]: editTaskReportAction as OnyxTypes.ReportAction}, }, { onyxMethod: Onyx.METHOD.MERGE, @@ -457,7 +457,7 @@ function editTask(report: OnyxTypes.Report, {title, description}: OnyxTypes.Task function editTaskAssignee(report: OnyxTypes.Report, ownerAccountID: number, assigneeEmail: string, assigneeAccountID = 0, assigneeChatReport: OnyxTypes.Report | null = null) { // Create the EditedReportAction on the task - const editTaskReportAction = ReportUtils.buildOptimisticEditedTaskReportAction(currentUserEmail ?? string); + const editTaskReportAction = ReportUtils.buildOptimisticEditedTaskReportAction(currentUserEmail ?? ''); const reportName = report.reportName?.trim(); let assigneeChatReportOnyxData; @@ -477,7 +477,7 @@ function editTaskAssignee(report: OnyxTypes.Report, ownerAccountID: number, assi { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`, - value: {[editTaskReportAction.reportActionID]: editTaskReportAction}, + value: {[editTaskReportAction.reportActionID]: editTaskReportAction as OnyxTypes.ReportAction}, }, { onyxMethod: Onyx.METHOD.MERGE, @@ -724,24 +724,24 @@ function cancelTask(taskReportID: string, taskTitle: string, originalStateNum: n const taskReport = ReportUtils.getReport(taskReportID); const parentReportAction = ReportActionsUtils.getParentReportAction(taskReport); const parentReport = ReportUtils.getParentReport(taskReport); - + const optimisticReportAction = { + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, + previousMessage: parentReportAction.message, + message: [ + { + translationKey: '', + type: 'COMMENT', + html: '', + text: '', + isEdited: true, + isDeletedParentAction: true, + }, + ], + errors: undefined, + linkMetaData: [], + }; const optimisticReportActions = { - [parentReportAction.reportActionID]: { - pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, - previousMessage: parentReportAction.message, - message: [ - { - translationKey: '', - type: 'COMMENT', - html: '', - text: '', - isEdited: true, - isDeletedParentAction: true, - }, - ], - errors: null, - linkMetaData: [], - }, + [parentReportAction.reportActionID]: optimisticReportAction, }; const optimisticData: OnyxUpdate[] = [ @@ -760,15 +760,17 @@ function cancelTask(taskReportID: string, taskTitle: string, originalStateNum: n onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${parentReport?.reportID}`, value: { - lastMessageText: ReportActionsUtils.getLastVisibleMessage(parentReport?.reportID, optimisticReportActions).lastMessageText, - lastVisibleActionCreated: ReportActionsUtils.getLastVisibleAction(parentReport?.reportID, optimisticReportActions) ?? 'created', + lastMessageText: ReportActionsUtils.getLastVisibleMessage(parentReport?.reportID ?? '', optimisticReportActions as unknown as OnyxTypes.ReportActions).lastMessageText ?? '', + lastVisibleActionCreated: + ReportActionsUtils.getLastVisibleAction(parentReport?.reportID ?? '', optimisticReportActions as unknown as OnyxTypes.ReportActions)?.childLastVisibleActionCreated ?? + 'created', }, }, { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${taskReportID}`, value: { - [optimisticReportActionID]: optimisticCancelReportAction, + [optimisticReportActionID]: optimisticCancelReportAction as OnyxTypes.ReportAction, }, }, { @@ -806,7 +808,7 @@ function cancelTask(taskReportID: string, taskTitle: string, originalStateNum: n value: { stateNum: originalStateNum, statusNum: originalStatusNum, - }, + } as OnyxTypes.Report, }, { onyxMethod: Onyx.METHOD.MERGE, diff --git a/src/types/onyx/Report.ts b/src/types/onyx/Report.ts index 138b2dc79ba1..0498afe916bc 100644 --- a/src/types/onyx/Report.ts +++ b/src/types/onyx/Report.ts @@ -147,6 +147,7 @@ type Report = { participantsList?: Array>; text?: string; privateNotes?: Record; + updateReportInLHN?: boolean; }; export default Report; diff --git a/src/types/onyx/ReportAction.ts b/src/types/onyx/ReportAction.ts index 64e1eb0b7c88..7bf6090adaf0 100644 --- a/src/types/onyx/ReportAction.ts +++ b/src/types/onyx/ReportAction.ts @@ -5,6 +5,48 @@ import * as OnyxCommon from './OnyxCommon'; import OriginalMessage, {Decision, Reaction} from './OriginalMessage'; import {Receipt} from './Transaction'; +type Image = { + /** The height of the image. */ + height?: number; + + /** The width of the image. */ + width?: number; + + /** The URL of the image. */ + url?: string; +}; + +type Logo = { + /** The height of the logo. */ + height?: number; + + /** The width of the logo. */ + width?: number; + + /** The URL of the logo. */ + url?: string; +}; + +type LinkMetaData = { + /** The URL of the link. */ + url?: string; + + /** A description of the link. */ + description?: string; + + /** The title of the link. */ + title?: string; + + /** The publisher of the link. */ + publisher?: string; + + /** The image associated with the link. */ + image: Image; + + /** The provider logo associated with the link. */ + logo: Logo; +}; + type Message = { /** The type of the action item fragment. Used to render a corresponding component */ type: string; @@ -79,6 +121,9 @@ type ReportActionBase = { /** report action message */ message?: Message[]; + /** report action previous message */ + previousMessage?: Message[]; + /** Whether we have received a response back from the server */ isLoading?: boolean; @@ -138,6 +183,7 @@ type ReportActionBase = { isAttachment?: boolean; childRecentReceiptTransactionIDs?: Record; reportID?: string; + linkMetaData?: LinkMetaData[]; }; type ReportAction = ReportActionBase & OriginalMessage; From 375eabbea4231cc09d418bc9e1b797a5b069295d Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Tue, 5 Dec 2023 10:47:27 -0500 Subject: [PATCH 021/161] Update src/libs/ReportUtils.ts Co-authored-by: Carlos Alvarez --- src/libs/ReportUtils.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 1412cfa73067..ad5ed0e46b20 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3417,8 +3417,7 @@ function shouldHideReport(report: OnyxEntry, currentReportId: string): b } /** - * - * Check if there are any violations belonging to the transaction in the transactionsViolations Onyx object + * Checks if there are any violations belonging to the transaction in the transactionsViolations Onyx object * then checks that the violation is of the proper type */ function transactionHasViolation(transactionID: string): boolean { From b6c24db81675e801039333344d093f7d7e12adc2 Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Tue, 5 Dec 2023 10:47:37 -0500 Subject: [PATCH 022/161] Update src/libs/ReportUtils.ts Co-authored-by: Carlos Alvarez --- 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 ad5ed0e46b20..0b6b95442cf4 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3430,7 +3430,7 @@ function transactionHasViolation(transactionID: string): boolean { /** * Checks to see if a report's parentAction is a money request that contains a violation - * This only pertains to report's that a user submitted and if it is open or processing + * This only applies to report submitter and for reports in the open and processing states */ function transactionThreadHasViolations(report: Report, betas: Beta[]): boolean { From ccd3eb33afc33ae4d8e98293eb75f043ca502ac4 Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Tue, 5 Dec 2023 15:47:39 -0500 Subject: [PATCH 023/161] remove unneeded changes --- src/libs/ReportUtils.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index b46cdfeabd34..c8ecb8576812 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -11,8 +11,6 @@ import {SvgProps} from 'react-native-svg'; import {ValueOf} from 'type-fest'; import * as Expensicons from '@components/Icon/Expensicons'; import * as defaultWorkspaceAvatars from '@components/Icon/WorkspaceDefaultAvatars'; -// eslint-disable-next-line @dword-design/import-alias/prefer-alias -import Permissions from '@libs/Permissions'; import CONST from '@src/CONST'; import {ParentNavigationSummaryParams, TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -33,6 +31,7 @@ import * as Localize from './Localize'; import linkingConfig from './Navigation/linkingConfig'; import Navigation from './Navigation/Navigation'; import * as NumberUtils from './NumberUtils'; +import Permissions from './Permissions'; import * as PolicyUtils from './PolicyUtils'; import * as ReportActionsUtils from './ReportActionsUtils'; import {LastVisibleMessage} from './ReportActionsUtils'; @@ -411,12 +410,7 @@ const transactionViolations: OnyxCollection = {}; Onyx.connect({ key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, callback: (violations, key) => { - if (!key) { - return; - } - - if (!violations) { - delete transactionViolations[key]; + if (!key || !violations) { return; } From 1d2cfa2caa60b595bc6d54a97a78595d6edeb225 Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Tue, 5 Dec 2023 16:13:20 -0500 Subject: [PATCH 024/161] Remove duplicate TRANSACTION_VIOLATIONS constant --- src/ONYXKEYS.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 82b6838cc75a..fc6d2927a1b1 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -265,7 +265,6 @@ const ONYXKEYS = { REPORT_USER_IS_LEAVING_ROOM: 'reportUserIsLeavingRoom_', SECURITY_GROUP: 'securityGroup_', TRANSACTION: 'transactions_', - TRANSACTION_VIOLATIONS: 'transactionViolations_', // Transaction Violations TRANSACTION_VIOLATIONS: 'transactionViolations_', From bcd1ee862bf896246fb5165ec4ffe0c6fb6dbb45 Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Wed, 6 Dec 2023 10:49:28 -0500 Subject: [PATCH 025/161] Update src/ONYXKEYS.ts Co-authored-by: Carlos Alvarez --- src/ONYXKEYS.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index fc6d2927a1b1..5f85b29cb281 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -265,8 +265,6 @@ const ONYXKEYS = { REPORT_USER_IS_LEAVING_ROOM: 'reportUserIsLeavingRoom_', SECURITY_GROUP: 'securityGroup_', TRANSACTION: 'transactions_', - - // Transaction Violations TRANSACTION_VIOLATIONS: 'transactionViolations_', // Holds temporary transactions used during the creation and edit flow From cae1e0d75c77a8bcb3f0a5c261e92ed231940614 Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Thu, 7 Dec 2023 15:13:59 -0500 Subject: [PATCH 026/161] Working on removing the onyx connect and using withOnyx --- .../LHNOptionsList/LHNOptionsList.js | 47 +++++++++--- .../LHNOptionsList/OptionRowLHNData.js | 7 +- .../ReportActionItem/ReportPreview.js | 24 ++++++- src/libs/OptionsListUtils.js | 4 +- src/libs/ReportUtils.ts | 71 ++++++++++--------- src/libs/SidebarUtils.ts | 12 ++-- 6 files changed, 112 insertions(+), 53 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.js b/src/components/LHNOptionsList/LHNOptionsList.js index 54830f5319f0..ab8101bddf45 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.js +++ b/src/components/LHNOptionsList/LHNOptionsList.js @@ -8,6 +8,7 @@ import _ from 'underscore'; import participantPropTypes from '@components/participantPropTypes'; import transactionPropTypes from '@components/transactionPropTypes'; import withCurrentReportID, {withCurrentReportIDDefaultProps, withCurrentReportIDPropTypes} from '@components/withCurrentReportID'; +import usePermissions from '@hooks/usePermissions'; import compose from '@libs/compose'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as ReportUtils from '@libs/ReportUtils'; @@ -66,8 +67,21 @@ const propTypes = { /** List of draft comments */ draftComments: PropTypes.objectOf(PropTypes.string), - /** The list of betas the user has access to */ - betas: PropTypes.arrayOf(PropTypes.string), + /** The list of transaction violations */ + transactionViolations: PropTypes.shape({ + violations: PropTypes.arrayOf( + PropTypes.shape({ + /** The transaction ID */ + transactionID: PropTypes.number, + + /** The transaction violation type */ + type: PropTypes.string, + + /** The transaction violation message */ + message: PropTypes.string, + }), + ), + }), ...withCurrentReportIDPropTypes, }; @@ -81,7 +95,7 @@ const defaultProps = { personalDetails: {}, transactions: {}, draftComments: {}, - betas: [], + transactionViolations: {}, ...withCurrentReportIDDefaultProps, }; @@ -102,9 +116,10 @@ function LHNOptionsList({ transactions, draftComments, currentReportID, - betas, + transactionViolations, }) { const styles = useThemeStyles(); + const {canUseViolations} = usePermissions(); /** * Function which renders a row in the list * @@ -142,11 +157,26 @@ function LHNOptionsList({ onSelectRow={onSelectRow} preferredLocale={preferredLocale} comment={itemComment} - betas={betas} + transactionViolations={transactionViolations} + canUseViolations={canUseViolations} /> ); }, - [currentReportID, draftComments, onSelectRow, optionMode, personalDetails, policy, preferredLocale, reportActions, reports, shouldDisableFocusOptions, transactions, betas], + [ + currentReportID, + draftComments, + onSelectRow, + optionMode, + personalDetails, + policy, + preferredLocale, + reportActions, + reports, + shouldDisableFocusOptions, + transactions, + transactionViolations, + canUseViolations, + ], ); return ( @@ -195,9 +225,8 @@ export default compose( draftComments: { key: ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, }, - betas: { - key: ONYXKEYS.BETAS, - initialValue: [], + transactionViolations: { + key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATION, }, }), )(LHNOptionsList); diff --git a/src/components/LHNOptionsList/OptionRowLHNData.js b/src/components/LHNOptionsList/OptionRowLHNData.js index 765c4998adce..975299bb8800 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.js +++ b/src/components/LHNOptionsList/OptionRowLHNData.js @@ -73,7 +73,8 @@ function OptionRowLHNData({ receiptTransactions, parentReportAction, transaction, - betas, + transactionViolations, + canUseViolations, ...propsToForward }) { const reportID = propsToForward.reportID; @@ -88,7 +89,7 @@ function OptionRowLHNData({ const optionItem = useMemo(() => { // Note: ideally we'd have this as a dependent selector in onyx! - const item = SidebarUtils.getOptionData(fullReport, reportActions, personalDetails, preferredLocale, policy, parentReportAction, betas); + const item = SidebarUtils.getOptionData(fullReport, reportActions, personalDetails, preferredLocale, policy, parentReportAction, transactionViolations, canUseViolations); if (deepEqual(item, optionItemRef.current)) { return optionItemRef.current; } @@ -97,7 +98,7 @@ function OptionRowLHNData({ // Listen parentReportAction to update title of thread report when parentReportAction changed // Listen to transaction to update title of transaction report when transaction changed // eslint-disable-next-line react-hooks/exhaustive-deps - }, [fullReport, linkedTransaction, reportActions, personalDetails, preferredLocale, policy, parentReportAction, transaction, betas]); + }, [fullReport, linkedTransaction, reportActions, personalDetails, preferredLocale, policy, parentReportAction, transaction, transactionViolations, canUseViolations]); useEffect(() => { if (!optionItem || optionItem.hasDraftComment || !comment || comment.length <= 0 || isFocused) { diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index 7f2b50534a2d..d6fdf099cbaa 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -91,6 +91,22 @@ const propTypes = { /** Whether a message is a whisper */ isWhisper: PropTypes.bool, + /** All of the transaction violations */ + transactionViolations: PropTypes.shape({ + violations: PropTypes.arrayOf( + PropTypes.shape({ + /** The transaction ID */ + transactionID: PropTypes.number, + + /** The transaction violation type */ + type: PropTypes.string, + + /** The transaction violation message */ + message: PropTypes.string, + }), + ), + }), + ...withLocalizePropTypes, }; @@ -104,6 +120,9 @@ const defaultProps = { accountID: null, }, isWhisper: false, + transactionViolations: { + violations: [], + }, }; function ReportPreview(props) { @@ -127,7 +146,7 @@ function ReportPreview(props) { const hasReceipts = transactionsWithReceipts.length > 0; const hasOnlyDistanceRequests = ReportUtils.hasOnlyDistanceRequestTransactions(props.iouReportID); const isScanning = hasReceipts && ReportUtils.areAllRequestsBeingSmartScanned(props.iouReportID, props.action); - const hasErrors = (hasReceipts && ReportUtils.hasMissingSmartscanFields(props.iouReportID)) || ReportUtils.reportHasViolations(props.iouReportID); + const hasErrors = (hasReceipts && ReportUtils.hasMissingSmartscanFields(props.iouReportID)) || ReportUtils.reportHasViolations(props.iouReportID, props.transactionViolations); const lastThreeTransactionsWithReceipts = transactionsWithReceipts.slice(-3); const lastThreeReceipts = _.map(lastThreeTransactionsWithReceipts, (transaction) => ReceiptUtils.getThumbnailAndImageURIs(transaction)); const hasNonReimbursableTransactions = ReportUtils.hasNonReimbursableTransactions(props.iouReportID); @@ -300,5 +319,8 @@ export default compose( session: { key: ONYXKEYS.SESSION, }, + transactionViolations: { + key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, + }, }), )(ReportPreview); diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index 4678ce395f76..43fdb14850a5 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -1127,7 +1127,9 @@ function getOptions( const searchValue = parsedPhoneNumber.possible ? parsedPhoneNumber.number.e164 : searchInputValue.toLowerCase(); // Filter out all the reports that shouldn't be displayed - const filteredReports = _.filter(reports, (report) => ReportUtils.shouldReportBeInOptionList(report, Navigation.getTopmostReportId(), false, betas, policies)); + const filteredReports = _.filter(reports, (report) => + ReportUtils.shouldReportBeInOptionList(report, Navigation.getTopmostReportId(), false, betas, policies, reportActions, false, transactionViolations), + ); // Sorting the reports works like this: // - Order everything by the last message timestamp (descending) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index c8ecb8576812..6b143b26418a 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -22,7 +22,7 @@ import {Message, ReportActions} from '@src/types/onyx/ReportAction'; import {Receipt, WaypointCollection} from '@src/types/onyx/Transaction'; import DeepValueOf from '@src/types/utils/DeepValueOf'; import {EmptyObject, isEmptyObject, isNotEmptyObject} from '@src/types/utils/EmptyObject'; -import * as CollectionUtils from './CollectionUtils'; +// import * as CollectionUtils from './CollectionUtils'; import * as CurrencyUtils from './CurrencyUtils'; import DateUtils from './DateUtils'; import isReportMessageAttachment from './isReportMessageAttachment'; @@ -406,31 +406,31 @@ Onyx.connect({ callback: (value) => (loginList = value), }); -const transactionViolations: OnyxCollection = {}; -Onyx.connect({ - key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, - callback: (violations, key) => { - if (!key || !violations) { - return; - } - - const transactionID = CollectionUtils.extractCollectionItemID(key); - transactionViolations[transactionID] = violations; - }, -}); - -const reportActions: OnyxCollection = {}; -Onyx.connect({ - key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, - callback: (actions, key) => { - if (!key || !actions) { - return; - } - - const reportID = CollectionUtils.extractCollectionItemID(key); - reportActions[reportID] = actions; - }, -}); +// const transactionViolations: OnyxCollection = {}; +// Onyx.connect({ +// key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, +// callback: (violations, key) => { +// if (!key || !violations) { +// return; +// } + +// const transactionID = CollectionUtils.extractCollectionItemID(key); +// transactionViolations[transactionID] = violations; +// }, +// }); + +// const reportActions: OnyxCollection = {}; +// Onyx.connect({ +// key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, +// callback: (actions, key) => { +// if (!key || !actions) { +// return; +// } + +// const reportID = CollectionUtils.extractCollectionItemID(key); +// reportActions[reportID] = actions; +// }, +// }); let allPolicyTags: Record = {}; @@ -3435,8 +3435,8 @@ function shouldHideReport(report: OnyxEntry, currentReportId: string): b * Checks if there are any violations belonging to the transaction in the transactionsViolations Onyx object * then checks that the violation is of the proper type */ -function transactionHasViolation(transactionID: string): boolean { - const violations = transactionViolations ? transactionViolations[transactionID]?.value : []; +function transactionHasViolation(transactionID: string, transactionViolations?: TransactionViolations): boolean { + const violations = transactionViolations ? transactionViolations[transactionID] : []; if (!violations) { return false; } @@ -3448,14 +3448,14 @@ function transactionHasViolation(transactionID: string): boolean { * This only applies to report submitter and for reports in the open and processing states */ -function transactionThreadHasViolations(report: Report, betas: Beta[]): boolean { - if (!Permissions.canUseViolations(betas) || !reportActions) { +function transactionThreadHasViolations(report: Report, canUseViolations: boolean, transactionViolations?: TransactionViolations, reportActions?: ReportActions | null): boolean { + if (!canUseViolations || !reportActions) { return false; } if (!report.parentReportActionID) { return false; } - const parentReportAction = reportActions[`${report.parentReportID}`]?.[`${report.parentReportActionID}`]; + const parentReportAction = reportActions[`${report.parentReportActionID}`]; if (!parentReportAction) { return false; } @@ -3472,15 +3472,15 @@ function transactionThreadHasViolations(report: Report, betas: Beta[]): boolean if (report?.stateNum !== CONST.REPORT.STATE_NUM.OPEN && report?.stateNum !== CONST.REPORT.STATE_NUM.PROCESSING) { return false; } - return transactionHasViolation(IOUTransactionID); + return transactionHasViolation(IOUTransactionID, transactionViolations); } /** * Checks to see if a report contains a violation */ -function reportHasViolations(reportID: string): boolean { +function reportHasViolations(reportID: string, transactionViolations: TransactionViolations): boolean { const transactions = TransactionUtils.getAllReportTransactions(reportID); - return transactions.some((transaction) => transactionHasViolation(transaction.transactionID)); + return transactions.some((transaction) => transactionHasViolation(transaction.transactionID, transactionViolations)); } /** @@ -3498,6 +3498,7 @@ function shouldReportBeInOptionList( policies: OnyxCollection, allReportActions?: OnyxCollection, excludeEmptyChats = false, + transactionViolations?: TransactionViolations, ) { const isInDefaultMode = !isInGSDMode; // Exclude reports that have no data because there wouldn't be anything to show in the option item. @@ -3558,7 +3559,7 @@ function shouldReportBeInOptionList( } // Always show IOU reports with violations - if (isExpenseRequest(report) && transactionThreadHasViolations(report, betas)) { + if (isExpenseRequest(report) && transactionThreadHasViolations(report, betas.includes(CONST.BETAS.VIOLATIONS), transactionViolations, allReportActions?.[report.reportID])) { return true; } diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 0c99aca64148..dce959a73872 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -4,7 +4,7 @@ import Onyx, {OnyxCollection} from 'react-native-onyx'; import {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import {PersonalDetails} from '@src/types/onyx'; +import {PersonalDetails, TransactionViolations} from '@src/types/onyx'; import Beta from '@src/types/onyx/Beta'; import * as OnyxCommon from '@src/types/onyx/OnyxCommon'; import Policy from '@src/types/onyx/Policy'; @@ -118,6 +118,7 @@ function getOrderedReportIDs( policies: Record, priorityMode: ValueOf, allReportActions: OnyxCollection, + transactionViolations: TransactionViolations, ): string[] { // Generate a unique cache key based on the function arguments const cachedReportsKey = JSON.stringify( @@ -150,7 +151,7 @@ function getOrderedReportIDs( 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, allReportActions, true), + ReportUtils.shouldReportBeInOptionList(report, currentReportId ?? '', isInGSDMode, betas, policies, allReportActions, true, transactionViolations), ); if (reportsToDisplay.length === 0) { @@ -237,7 +238,8 @@ function getOptionData( preferredLocale: ValueOf, policy: Policy, parentReportAction: ReportAction, - betas: Beta[], + transactionViolations: TransactionViolations, + canUseViolations: boolean, ): ReportUtils.OptionData | undefined { // When a user signs out, Onyx is cleared. Due to the lazy rendering with a virtual list, it's possible for // this method to be called after the Onyx data has been cleared out. In that case, it's fine to do @@ -291,7 +293,9 @@ function getOptionData( result.pendingAction = report.pendingFields ? report.pendingFields.addWorkspaceRoom || report.pendingFields.createChat : null; result.allReportErrors = OptionsListUtils.getAllReportErrors(report, reportActions) as OnyxCommon.Errors; result.brickRoadIndicator = - Object.keys(result.allReportErrors ?? {}).length !== 0 || ReportUtils.transactionThreadHasViolations(report, betas) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''; + Object.keys(result.allReportErrors ?? {}).length !== 0 || ReportUtils.transactionThreadHasViolations(report, canUseViolations, transactionViolations, reportActions) + ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR + : ''; result.ownerAccountID = report.ownerAccountID; result.managerID = report.managerID; result.reportID = report.reportID; From 90b7138e07a082816778408d16e99d645e029481 Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Mon, 11 Dec 2023 16:13:00 -0500 Subject: [PATCH 027/161] Starting so swap in the transactionViolations --- src/pages/home/report/ReportActionItem.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index 92bb370155c9..f743d75cb590 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -342,6 +342,7 @@ function ReportActionItem(props) { contextMenuAnchor={popoverAnchorRef} checkIfContextMenuActive={toggleContextMenuFromActiveReportAction} isWhisper={isWhisper} + transactionViolations={props.transactionViolations} /> ); } else if ( From 35de23f38d48365b681a8d7aef348c7c3cc135b5 Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Tue, 12 Dec 2023 09:35:19 -0500 Subject: [PATCH 028/161] Fixed tests and created new hook mock --- src/components/LHNOptionsList/LHNOptionsList.js | 2 +- src/hooks/__mocks__/usePermissions.ts | 11 +++++++++++ src/libs/OptionsListUtils.js | 1 + tests/perf-test/SidebarUtils.perf-test.ts | 9 +++++---- tests/unit/SidebarFilterTest.js | 1 + tests/unit/SidebarOrderTest.js | 1 + tests/unit/SidebarTest.js | 1 + 7 files changed, 21 insertions(+), 5 deletions(-) create mode 100644 src/hooks/__mocks__/usePermissions.ts diff --git a/src/components/LHNOptionsList/LHNOptionsList.js b/src/components/LHNOptionsList/LHNOptionsList.js index ab8101bddf45..d2dc958d811f 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.js +++ b/src/components/LHNOptionsList/LHNOptionsList.js @@ -226,7 +226,7 @@ export default compose( key: ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, }, transactionViolations: { - key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATION, + key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, }, }), )(LHNOptionsList); diff --git a/src/hooks/__mocks__/usePermissions.ts b/src/hooks/__mocks__/usePermissions.ts new file mode 100644 index 000000000000..4af004095712 --- /dev/null +++ b/src/hooks/__mocks__/usePermissions.ts @@ -0,0 +1,11 @@ +import UsePermissions from '@hooks/usePermissions'; + +/** + * + * @returns {UsePermissions} A mock of the usePermissions hook. + */ + +const usePermissions = (): typeof UsePermissions => () => ({ + canUseViolations: true, +}); +export default usePermissions; diff --git a/src/libs/OptionsListUtils.js b/src/libs/OptionsListUtils.js index 43fdb14850a5..b431505fa9d6 100644 --- a/src/libs/OptionsListUtils.js +++ b/src/libs/OptionsListUtils.js @@ -1081,6 +1081,7 @@ function getOptions( recentlyUsedTags = [], canInviteUser = true, includeSelectedOptions = false, + transactionViolations = {}, }, ) { if (includeCategories) { diff --git a/tests/perf-test/SidebarUtils.perf-test.ts b/tests/perf-test/SidebarUtils.perf-test.ts index fb3cac24053a..4424a66d3936 100644 --- a/tests/perf-test/SidebarUtils.perf-test.ts +++ b/tests/perf-test/SidebarUtils.perf-test.ts @@ -3,7 +3,7 @@ import {measureFunction} from 'reassure'; import SidebarUtils from '@libs/SidebarUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import {Beta, PersonalDetails} from '@src/types/onyx'; +import {PersonalDetails, TransactionViolations} from '@src/types/onyx'; import Policy from '@src/types/onyx/Policy'; import Report from '@src/types/onyx/Report'; import ReportAction, {ReportActions} from '@src/types/onyx/ReportAction'; @@ -51,20 +51,21 @@ test('getOptionData on 5k reports', async () => { const preferredLocale = 'en'; const policy = createRandomPolicy(1); const parentReportAction = createRandomReportAction(1); - const betas: Beta[] = []; + const transactionViolations = {} as TransactionViolations; Onyx.multiSet({ ...mockedResponseMap, }); await waitForBatchedUpdates(); - await measureFunction(() => SidebarUtils.getOptionData(report, reportActions, personalDetails, preferredLocale, policy, parentReportAction, betas), {runs}); + await measureFunction(() => SidebarUtils.getOptionData(report, reportActions, personalDetails, preferredLocale, policy, parentReportAction, transactionViolations, false), {runs}); }); test('getOrderedReportIDs on 5k reports', async () => { const currentReportId = '1'; const allReports = getMockedReports(); const betas = [CONST.BETAS.DEFAULT_ROOMS, CONST.BETAS.POLICY_ROOMS]; + const transactionViolations = {} as TransactionViolations; const policies = createCollection( (item) => `${ONYXKEYS.COLLECTION.POLICY}${item.id}`, @@ -94,5 +95,5 @@ test('getOrderedReportIDs on 5k reports', async () => { }); await waitForBatchedUpdates(); - await measureFunction(() => SidebarUtils.getOrderedReportIDs(currentReportId, allReports, betas, policies, CONST.PRIORITY_MODE.DEFAULT, allReportActions), {runs}); + await measureFunction(() => SidebarUtils.getOrderedReportIDs(currentReportId, allReports, betas, policies, CONST.PRIORITY_MODE.DEFAULT, allReportActions, transactionViolations), {runs}); }); diff --git a/tests/unit/SidebarFilterTest.js b/tests/unit/SidebarFilterTest.js index 23a958e3aa9d..4ecddfb8fd9e 100644 --- a/tests/unit/SidebarFilterTest.js +++ b/tests/unit/SidebarFilterTest.js @@ -10,6 +10,7 @@ import wrapOnyxWithWaitForBatchedUpdates from '../utils/wrapOnyxWithWaitForBatch // Be sure to include the mocked permissions library or else the beta tests won't work jest.mock('../../src/libs/Permissions'); +jest.mock('../../src/hooks/usePermissions.ts'); const ONYXKEYS = { PERSONAL_DETAILS_LIST: 'personalDetailsList', diff --git a/tests/unit/SidebarOrderTest.js b/tests/unit/SidebarOrderTest.js index 6eef3e40bf1c..d9ab881a442e 100644 --- a/tests/unit/SidebarOrderTest.js +++ b/tests/unit/SidebarOrderTest.js @@ -11,6 +11,7 @@ import wrapOnyxWithWaitForBatchedUpdates from '../utils/wrapOnyxWithWaitForBatch // Be sure to include the mocked Permissions and Expensicons libraries or else the beta tests won't work jest.mock('../../src/libs/Permissions'); +jest.mock('../../src/hooks/usePermissions.ts'); jest.mock('../../src/components/Icon/Expensicons'); const ONYXKEYS = { diff --git a/tests/unit/SidebarTest.js b/tests/unit/SidebarTest.js index 4bd0795aa3b9..70c7a33a8dc6 100644 --- a/tests/unit/SidebarTest.js +++ b/tests/unit/SidebarTest.js @@ -9,6 +9,7 @@ import wrapOnyxWithWaitForBatchedUpdates from '../utils/wrapOnyxWithWaitForBatch // Be sure to include the mocked Permissions and Expensicons libraries or else the beta tests won't work jest.mock('../../src/libs/Permissions'); +jest.mock('../../src/hooks/usePermissions.ts'); jest.mock('../../src/components/Icon/Expensicons'); const ONYXKEYS = { From bbfd1fa60a61af863c91e23472c17e26056f3151 Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Tue, 12 Dec 2023 11:36:13 -0500 Subject: [PATCH 029/161] Switched to just passing down parentReportAction --- src/libs/ReportUtils.ts | 17 +++++++---------- src/libs/SidebarUtils.ts | 2 +- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 6b143b26418a..ecb7041771f6 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3448,15 +3448,9 @@ function transactionHasViolation(transactionID: string, transactionViolations?: * This only applies to report submitter and for reports in the open and processing states */ -function transactionThreadHasViolations(report: Report, canUseViolations: boolean, transactionViolations?: TransactionViolations, reportActions?: ReportActions | null): boolean { - if (!canUseViolations || !reportActions) { - return false; - } - if (!report.parentReportActionID) { - return false; - } - const parentReportAction = reportActions[`${report.parentReportActionID}`]; - if (!parentReportAction) { +function transactionThreadHasViolations(report: Report, canUseViolations: boolean, transactionViolations?: TransactionViolations, parentReportAction?: ReportAction | null): boolean { + console.log('Parent Report Action : ', parentReportAction); + if (!canUseViolations || !parentReportAction) { return false; } if (parentReportAction.actionName !== CONST.REPORT.ACTIONS.TYPE.IOU) { @@ -3559,7 +3553,10 @@ function shouldReportBeInOptionList( } // Always show IOU reports with violations - if (isExpenseRequest(report) && transactionThreadHasViolations(report, betas.includes(CONST.BETAS.VIOLATIONS), transactionViolations, allReportActions?.[report.reportID])) { + if ( + isExpenseRequest(report) && + transactionThreadHasViolations(report, betas.includes(CONST.BETAS.VIOLATIONS), transactionViolations, allReportActions?.[`${report.parentReportActionID}`]) + ) { return true; } diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index dce959a73872..15c6e4a4c235 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -293,7 +293,7 @@ function getOptionData( result.pendingAction = report.pendingFields ? report.pendingFields.addWorkspaceRoom || report.pendingFields.createChat : null; result.allReportErrors = OptionsListUtils.getAllReportErrors(report, reportActions) as OnyxCommon.Errors; result.brickRoadIndicator = - Object.keys(result.allReportErrors ?? {}).length !== 0 || ReportUtils.transactionThreadHasViolations(report, canUseViolations, transactionViolations, reportActions) + Object.keys(result.allReportErrors ?? {}).length !== 0 || ReportUtils.transactionThreadHasViolations(report, canUseViolations, transactionViolations, parentReportAction) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''; result.ownerAccountID = report.ownerAccountID; From 2c52edf9bfe2777b3b6a0be3762a044259b622d8 Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Tue, 12 Dec 2023 15:53:04 -0500 Subject: [PATCH 030/161] Move the report actions selector to the SidebarUtils file --- src/libs/ReportUtils.ts | 12 +++-- src/libs/SidebarUtils.ts | 52 ++++++++++++++++++++-- src/pages/home/sidebar/SidebarLinksData.js | 48 +++++++++++--------- tests/perf-test/SidebarUtils.perf-test.ts | 4 +- 4 files changed, 85 insertions(+), 31 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 3d105cc548fa..30a805d1b9c8 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3504,7 +3504,7 @@ function shouldHideReport(report: OnyxEntry, currentReportId: string): b * then checks that the violation is of the proper type */ function transactionHasViolation(transactionID: string, transactionViolations?: TransactionViolations): boolean { - const violations = transactionViolations ? transactionViolations[transactionID] : []; + const violations: TransactionViolation[] = transactionViolations ? transactionViolations[transactionID] || [] : []; if (!violations) { return false; } @@ -3517,8 +3517,7 @@ function transactionHasViolation(transactionID: string, transactionViolations?: */ function transactionThreadHasViolations(report: Report, canUseViolations: boolean, transactionViolations?: TransactionViolations, parentReportAction?: ReportAction | null): boolean { - console.log('Parent Report Action : ', parentReportAction); - if (!canUseViolations || !parentReportAction) { + if (!parentReportAction) { return false; } if (parentReportAction.actionName !== CONST.REPORT.ACTIONS.TYPE.IOU) { @@ -3623,7 +3622,12 @@ function shouldReportBeInOptionList( // Always show IOU reports with violations if ( isExpenseRequest(report) && - transactionThreadHasViolations(report, betas.includes(CONST.BETAS.VIOLATIONS), transactionViolations, allReportActions?.[`${report.parentReportActionID}`]) + transactionThreadHasViolations( + report, + betas.includes(CONST.BETAS.VIOLATIONS), + transactionViolations, + allReportActions?.[`${report.parentReportID}`]?.[`${report.parentReportActionID}`], + ) ) { return true; } diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 2a09758b26d5..8db53e46882b 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -8,7 +8,7 @@ import {PersonalDetails, TransactionViolations} from '@src/types/onyx'; import Beta from '@src/types/onyx/Beta'; import * as OnyxCommon from '@src/types/onyx/OnyxCommon'; import Policy from '@src/types/onyx/Policy'; -import Report from '@src/types/onyx/Report'; +import Report, {Message} from '@src/types/onyx/Report'; import ReportAction, {ReportActions} from '@src/types/onyx/ReportAction'; import * as CollectionUtils from './CollectionUtils'; import * as LocalePhoneNumber from './LocalePhoneNumber'; @@ -108,6 +108,51 @@ function setWithLimit(map: Map, key: TKey, value: TV // Variable to verify if ONYX actions are loaded let hasInitialReportActions = false; +// Originally we were passing down the reportActions as the type OnyxCollection but +// it was used as a Record in the cachedReportsKey. This was causing a type error +// for the ReportUtils.shouldReportBeInOptionList function. To fix this, we are now passing down the reportActions +// as a Record and then converting it to a Record in the +// reportActionsSelector function. +// This function was originally in the parent component as an Onyx selector, but it was moved here to prevent +// to simplify the logic and prevent the need to pass down the reportActions as an array. + +type MappedReportAction = { + id: string; + errors: OnyxCommon.Errors; + message: Message[]; +}; + +type MappedReportActions = Record; + +const reportActionsSelector = (reportActions?: MappedReportActions): Record | undefined => { + if (!reportActions) { + return undefined; + } + + return Object.values(reportActions).reduce((acc, reportAction) => { + const modifiedAction: MappedReportAction = { + id: reportAction.id, + errors: reportAction.errors || [], + message: [ + { + moderationDecision: { + decision: reportAction.message?.[0]?.moderationDecision?.decision, + }, + }, + ], + }; + + // If the key already exists, append to the array, otherwise create a new array + if (acc[reportAction.id]) { + acc[reportAction.id].push(modifiedAction); + } else { + acc[reportAction.id] = [modifiedAction]; + } + + return acc; + }, {} as Record); +}; + /** * @returns An array of reportIDs sorted in the proper order */ @@ -117,13 +162,14 @@ function getOrderedReportIDs( betas: Beta[], policies: Record, priorityMode: ValueOf, - allReportActions: OnyxCollection, + allReportActions: OnyxCollection, transactionViolations: TransactionViolations, ): string[] { + const mappedReportActions = reportActionsSelector(allReportActions as unknown as MappedReportActions); // 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], + [currentReportId, allReports, betas, policies, priorityMode, mappedReportActions?.[`${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, diff --git a/src/pages/home/sidebar/SidebarLinksData.js b/src/pages/home/sidebar/SidebarLinksData.js index 97f119bbd2e8..856af9fd5223 100644 --- a/src/pages/home/sidebar/SidebarLinksData.js +++ b/src/pages/home/sidebar/SidebarLinksData.js @@ -1,5 +1,4 @@ import {deepEqual} from 'fast-equals'; -import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import React, {useCallback, useMemo, useRef} from 'react'; import {View} from 'react-native'; @@ -55,6 +54,22 @@ const propTypes = { /** The policies which the user has access to */ // eslint-disable-next-line react/forbid-prop-types policies: PropTypes.object, + + /** All of the transaction violations */ + transactionViolations: PropTypes.shape({ + violations: PropTypes.arrayOf( + PropTypes.shape({ + /** The transaction ID */ + transactionID: PropTypes.number, + + /** The transaction violation type */ + type: PropTypes.string, + + /** The transaction violation message */ + message: PropTypes.string, + }), + ), + }), }; const defaultProps = { @@ -64,16 +79,17 @@ const defaultProps = { priorityMode: CONST.PRIORITY_MODE.DEFAULT, betas: [], policies: {}, + transactionViolations: {}, }; -function SidebarLinksData({isFocused, allReportActions, betas, chatReports, currentReportID, insets, isLoadingApp, onLinkClick, policies, priorityMode, network}) { +function SidebarLinksData({isFocused, allReportActions, betas, chatReports, currentReportID, insets, isLoadingApp, onLinkClick, policies, priorityMode, network, transactionViolations}) { const styles = useThemeStyles(); const {translate} = useLocalize(); const reportIDsRef = useRef(null); const isLoading = isLoadingApp; const optionListItems = useMemo(() => { - const reportIDs = SidebarUtils.getOrderedReportIDs(null, chatReports, betas, policies, priorityMode, allReportActions); + const reportIDs = SidebarUtils.getOrderedReportIDs(null, chatReports, betas, policies, priorityMode, allReportActions, transactionViolations); if (deepEqual(reportIDsRef.current, reportIDs)) { return reportIDsRef.current; @@ -85,7 +101,7 @@ function SidebarLinksData({isFocused, allReportActions, betas, chatReports, curr reportIDsRef.current = reportIDs; } return reportIDsRef.current || []; - }, [allReportActions, betas, chatReports, policies, priorityMode, isLoading, network.isOffline]); + }, [allReportActions, betas, chatReports, policies, priorityMode, isLoading, network.isOffline, transactionViolations]); // We need to make sure the current report is in the list of reports, but we do not want // to have to re-generate the list every time the currentReportID changes. To do that @@ -94,10 +110,10 @@ function SidebarLinksData({isFocused, allReportActions, betas, chatReports, curr // case we re-generate the list a 2nd time with the current report included. const optionListItemsWithCurrentReport = useMemo(() => { if (currentReportID && !_.contains(optionListItems, currentReportID)) { - return SidebarUtils.getOrderedReportIDs(currentReportID, chatReports, betas, policies, priorityMode, allReportActions); + return SidebarUtils.getOrderedReportIDs(currentReportID, chatReports, betas, policies, priorityMode, allReportActions, transactionViolations); } return optionListItems; - }, [currentReportID, optionListItems, chatReports, betas, policies, priorityMode, allReportActions]); + }, [currentReportID, optionListItems, chatReports, betas, policies, priorityMode, allReportActions, transactionViolations]); const currentReportIDRef = useRef(currentReportID); currentReportIDRef.current = currentReportID; @@ -170,21 +186,6 @@ const chatReportSelector = (report) => parentReportID: report.parentReportID, }; -/** - * @param {Object} [reportActions] - * @returns {Object|undefined} - */ -const reportActionsSelector = (reportActions) => - reportActions && - _.map(reportActions, (reportAction) => ({ - errors: lodashGet(reportAction, 'errors', []), - message: [ - { - moderationDecision: {decision: lodashGet(reportAction, 'message[0].moderationDecision.decision')}, - }, - ], - })); - /** * @param {Object} [policy] * @returns {Object|undefined} @@ -219,7 +220,6 @@ export default compose( }, allReportActions: { key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, - selector: reportActionsSelector, initialValue: {}, }, policies: { @@ -227,5 +227,9 @@ export default compose( selector: policySelector, initialValue: {}, }, + transactionViolations: { + key: ONYXKEYS.TRANSACTION_VIOLATIONS, + initialValue: {}, + }, }), )(SidebarLinksData); diff --git a/tests/perf-test/SidebarUtils.perf-test.ts b/tests/perf-test/SidebarUtils.perf-test.ts index 11e90b83ebc5..b627d54edc5d 100644 --- a/tests/perf-test/SidebarUtils.perf-test.ts +++ b/tests/perf-test/SidebarUtils.perf-test.ts @@ -6,7 +6,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import {PersonalDetails, TransactionViolations} from '@src/types/onyx'; import Policy from '@src/types/onyx/Policy'; import Report from '@src/types/onyx/Report'; -import ReportAction from '@src/types/onyx/ReportAction'; +import ReportAction, {ReportActions} from '@src/types/onyx/ReportAction'; import createCollection from '../utils/collections/createCollection'; import createPersonalDetails from '../utils/collections/personalDetails'; import createRandomPolicy from '../utils/collections/policies'; @@ -88,7 +88,7 @@ test('[SidebarUtils] getOrderedReportIDs on 5k reports', async () => { }, ], ]), - ) as unknown as OnyxCollection; + ) as unknown as OnyxCollection; Onyx.multiSet({ ...mockedResponseMap, From 73971f074a22e97379e36d6faba327a4089264a4 Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Tue, 12 Dec 2023 20:08:31 -0500 Subject: [PATCH 031/161] Correct type import --- src/libs/SidebarUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 8db53e46882b..5001725dfbb5 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -8,8 +8,8 @@ import {PersonalDetails, TransactionViolations} from '@src/types/onyx'; import Beta from '@src/types/onyx/Beta'; import * as OnyxCommon from '@src/types/onyx/OnyxCommon'; import Policy from '@src/types/onyx/Policy'; -import Report, {Message} from '@src/types/onyx/Report'; -import ReportAction, {ReportActions} from '@src/types/onyx/ReportAction'; +import Report from '@src/types/onyx/Report'; +import ReportAction, {Message, ReportActions} from '@src/types/onyx/ReportAction'; import * as CollectionUtils from './CollectionUtils'; import * as LocalePhoneNumber from './LocalePhoneNumber'; import * as Localize from './Localize'; From 1234b8890d0220ab59d16ca585e5df1a1091cbba Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Tue, 12 Dec 2023 20:19:38 -0500 Subject: [PATCH 032/161] Another type correction --- src/libs/SidebarUtils.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 5001725dfbb5..acf58424c003 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -135,9 +135,9 @@ const reportActionsSelector = (reportActions?: MappedReportActions): Record Date: Tue, 12 Dec 2023 20:54:54 -0500 Subject: [PATCH 033/161] Final fix for the test of the report actions --- src/libs/SidebarUtils.ts | 51 ++++++++++++++++++++++------------------ 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index acf58424c003..8db1ca430626 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -117,40 +117,45 @@ let hasInitialReportActions = false; // to simplify the logic and prevent the need to pass down the reportActions as an array. type MappedReportAction = { - id: string; + reportActionID: string; errors: OnyxCommon.Errors; message: Message[]; }; type MappedReportActions = Record; -const reportActionsSelector = (reportActions?: MappedReportActions): Record | undefined => { - if (!reportActions) { +const reportActionsSelector = (reportActionsCollection?: Record): Record | undefined => { + if (!reportActionsCollection) { return undefined; } - return Object.values(reportActions).reduce((acc, reportAction) => { - const modifiedAction: MappedReportAction = { - id: reportAction.id, - errors: reportAction.errors || [], - message: [ - { - moderationDecision: reportAction.message?.[0]?.moderationDecision, - type: reportAction.message?.[0]?.type, - text: reportAction.message?.[0]?.text, - }, - ], - }; - - // If the key already exists, append to the array, otherwise create a new array - if (acc[reportAction.id]) { - acc[reportAction.id].push(modifiedAction); - } else { - acc[reportAction.id] = [modifiedAction]; + return Object.values(reportActionsCollection).reduce>((acc, reportActions) => { + if (!reportActions) { + return acc; } + Object.entries(reportActions).forEach(([reportActionID, reportAction]) => { + const modifiedAction: MappedReportAction = { + reportActionID, + errors: reportAction.errors || [], + message: [ + { + moderationDecision: reportAction.message?.[0]?.moderationDecision, + type: reportAction.message?.[0]?.type, + text: reportAction.message?.[0]?.text, + }, + ], + }; + + // If the key already exists, append to the array, otherwise create a new array + if (acc[reportActionID]) { + acc[reportActionID].push(modifiedAction); + } else { + acc[reportActionID] = [modifiedAction]; + } + }); return acc; - }, {} as Record); + }, {}); }; /** @@ -165,7 +170,7 @@ function getOrderedReportIDs( allReportActions: OnyxCollection, transactionViolations: TransactionViolations, ): string[] { - const mappedReportActions = reportActionsSelector(allReportActions as unknown as MappedReportActions); + const mappedReportActions = allReportActions ? reportActionsSelector(allReportActions as unknown as Record) : {}; // Generate a unique cache key based on the function arguments const cachedReportsKey = JSON.stringify( // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing From 4366d642a53131a693f15963aa6804caf0062526 Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Tue, 12 Dec 2023 22:15:21 -0500 Subject: [PATCH 034/161] Update Comments --- src/libs/ReportUtils.ts | 26 -------------------------- src/libs/SidebarUtils.ts | 2 +- 2 files changed, 1 insertion(+), 27 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index cca13ecbc1c2..cc933e62b244 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -416,32 +416,6 @@ Onyx.connect({ callback: (value) => (loginList = value), }); -// const transactionViolations: OnyxCollection = {}; -// Onyx.connect({ -// key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, -// callback: (violations, key) => { -// if (!key || !violations) { -// return; -// } - -// const transactionID = CollectionUtils.extractCollectionItemID(key); -// transactionViolations[transactionID] = violations; -// }, -// }); - -// const reportActions: OnyxCollection = {}; -// Onyx.connect({ -// key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, -// callback: (actions, key) => { -// if (!key || !actions) { -// return; -// } - -// const reportID = CollectionUtils.extractCollectionItemID(key); -// reportActions[reportID] = actions; -// }, -// }); - let allPolicyTags: Record = {}; Onyx.connect({ diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 8db1ca430626..d550d899e078 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -113,7 +113,7 @@ let hasInitialReportActions = false; // for the ReportUtils.shouldReportBeInOptionList function. To fix this, we are now passing down the reportActions // as a Record and then converting it to a Record in the // reportActionsSelector function. -// This function was originally in the parent component as an Onyx selector, but it was moved here to prevent +// This function was originally in the parent component as an Onyx selector, but it was moved here // to simplify the logic and prevent the need to pass down the reportActions as an array. type MappedReportAction = { From 57168006f2a429f21c2f016f0ec51054d98a3971 Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Tue, 12 Dec 2023 22:29:03 -0500 Subject: [PATCH 035/161] A few fixes --- src/components/LHNOptionsList/LHNOptionsList.js | 9 +++++++++ src/components/ReportActionItem/ReportPreview.js | 9 +++++++++ src/pages/home/sidebar/SidebarLinksData.js | 9 +++++++++ tests/perf-test/ReportUtils.perf-test.ts | 2 +- tests/perf-test/SidebarLinks.perf-test.js | 1 + 5 files changed, 29 insertions(+), 1 deletion(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.js b/src/components/LHNOptionsList/LHNOptionsList.js index d2dc958d811f..6d02c26d1753 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.js +++ b/src/components/LHNOptionsList/LHNOptionsList.js @@ -79,6 +79,15 @@ const propTypes = { /** The transaction violation message */ message: PropTypes.string, + + /** The transaction violation data */ + data: PropTypes.shape({ + /** The transaction violation data field */ + field: PropTypes.string, + + /** The transaction violation data value */ + value: PropTypes.string, + }), }), ), }), diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index ab6fa5173a16..e88100ff7567 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -112,6 +112,15 @@ const propTypes = { /** The transaction violation message */ message: PropTypes.string, + + /** The transaction violation data */ + data: PropTypes.shape({ + /** The transaction violation data field */ + field: PropTypes.string, + + /** The transaction violation data value */ + value: PropTypes.string, + }), }), ), }), diff --git a/src/pages/home/sidebar/SidebarLinksData.js b/src/pages/home/sidebar/SidebarLinksData.js index 856af9fd5223..a5788a91794a 100644 --- a/src/pages/home/sidebar/SidebarLinksData.js +++ b/src/pages/home/sidebar/SidebarLinksData.js @@ -67,6 +67,15 @@ const propTypes = { /** The transaction violation message */ message: PropTypes.string, + + /** The transaction violation data */ + data: PropTypes.shape({ + /** The transaction violation data field */ + field: PropTypes.string, + + /** The transaction violation data value */ + value: PropTypes.string, + }), }), ), }), diff --git a/tests/perf-test/ReportUtils.perf-test.ts b/tests/perf-test/ReportUtils.perf-test.ts index ab6ee72a0082..03c79095b6a7 100644 --- a/tests/perf-test/ReportUtils.perf-test.ts +++ b/tests/perf-test/ReportUtils.perf-test.ts @@ -183,7 +183,7 @@ test('[ReportUtils] shouldReportBeInOptionList on 1k participant', async () => { const policies = getMockedPolicies(); await waitForBatchedUpdates(); - await measureFunction(() => ReportUtils.shouldReportBeInOptionList(report, currentReportId, isInGSDMode, betas, policies), {runs}); + await measureFunction(() => ReportUtils.shouldReportBeInOptionList(report, currentReportId, isInGSDMode, betas, policies, {}), {runs}); }); test('[ReportUtils] getWorkspaceIcon on 5k policies', async () => { diff --git a/tests/perf-test/SidebarLinks.perf-test.js b/tests/perf-test/SidebarLinks.perf-test.js index c6e6c024c597..0b05a14fa067 100644 --- a/tests/perf-test/SidebarLinks.perf-test.js +++ b/tests/perf-test/SidebarLinks.perf-test.js @@ -10,6 +10,7 @@ import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; import wrapOnyxWithWaitForBatchedUpdates from '../utils/wrapOnyxWithWaitForBatchedUpdates'; jest.mock('../../src/libs/Permissions'); +jest.mock('../../src/hooks/usePermissions.ts'); jest.mock('../../src/libs/Navigation/Navigation'); jest.mock('../../src/components/Icon/Expensicons'); From adf19333272d0158099c63d05ae584eccc70ec97 Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Wed, 13 Dec 2023 09:15:04 -0500 Subject: [PATCH 036/161] Fixed some prop types and also added a beta guard --- .../ReportActionItem/ReportPreview.js | 6 +++- src/libs/ReportUtils.ts | 32 ++++++++++--------- src/libs/SidebarUtils.ts | 2 +- src/pages/home/sidebar/SidebarLinksData.js | 18 +++-------- 4 files changed, 27 insertions(+), 31 deletions(-) diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index e88100ff7567..005f244cb6b1 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -19,6 +19,7 @@ import ControlSelection from '@libs/ControlSelection'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import Navigation from '@libs/Navigation/Navigation'; +import {usePermissions} from '@libs/Permissions'; import * as ReceiptUtils from '@libs/ReceiptUtils'; import * as ReportActionUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; @@ -149,6 +150,8 @@ function ReportPreview(props) { const styles = useThemeStyles(); const {translate} = useLocalize(); + const {canUseViolations} = usePermissions(); + const managerID = props.iouReport.managerID || 0; const isCurrentUserManager = managerID === lodashGet(props.session, 'accountID'); const {totalDisplaySpend, reimbursableSpend} = ReportUtils.getMoneyRequestSpendBreakdown(props.iouReport); @@ -167,7 +170,8 @@ function ReportPreview(props) { const hasReceipts = transactionsWithReceipts.length > 0; const hasOnlyDistanceRequests = ReportUtils.hasOnlyDistanceRequestTransactions(props.iouReportID); const isScanning = hasReceipts && ReportUtils.areAllRequestsBeingSmartScanned(props.iouReportID, props.action); - const hasErrors = (hasReceipts && ReportUtils.hasMissingSmartscanFields(props.iouReportID)) || ReportUtils.reportHasViolations(props.iouReportID, props.transactionViolations); + const hasErrors = + (hasReceipts && ReportUtils.hasMissingSmartscanFields(props.iouReportID)) || (canUseViolations && ReportUtils.reportHasViolations(props.iouReportID, props.transactionViolations)); const lastThreeTransactionsWithReceipts = transactionsWithReceipts.slice(-3); const lastThreeReceipts = _.map(lastThreeTransactionsWithReceipts, (transaction) => ReceiptUtils.getThumbnailAndImageURIs(transaction)); const hasNonReimbursableTransactions = ReportUtils.hasNonReimbursableTransactions(props.iouReportID); diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index cc933e62b244..08e2cd44dc67 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3468,8 +3468,8 @@ function canAccessReport(report: OnyxEntry, policies: OnyxCollection, currentReportId: string): boolean { const currentReport = getReport(currentReportId); const parentReport = getParentReport(isNotEmptyObject(currentReport) ? currentReport : null); - const allReportActions = ReportActionsUtils.getAllReportActions(report?.reportID ?? ''); - const isChildReportHasComment = Object.values(allReportActions ?? {})?.some((reportAction) => (reportAction?.childVisibleActionCount ?? 0) > 0); + const reportActions = ReportActionsUtils.getAllReportActions(report?.reportID ?? ''); + const isChildReportHasComment = Object.values(reportActions ?? {})?.some((reportAction) => (reportAction?.childVisibleActionCount ?? 0) > 0); return parentReport?.reportID !== report?.reportID && !isChildReportHasComment; } @@ -3490,14 +3490,24 @@ function transactionHasViolation(transactionID: string, transactionViolations?: * This only applies to report submitter and for reports in the open and processing states */ -function transactionThreadHasViolations(report: Report, canUseViolations: boolean, transactionViolations?: TransactionViolations, parentReportAction?: ReportAction | null): boolean { - if (!parentReportAction) { +function transactionThreadHasViolations( + report: Report, + canUseViolations: boolean, + transactionViolations?: TransactionViolations, + reportActions?: OnyxCollection | null, + parentReportAction?: ReportAction | null, +): boolean { + if (!canUseViolations || !reportActions) { + return false; + } + const resolvedParentReportAction = parentReportAction ?? reportActions?.[`${report.parentReportID}`]?.[`${report.parentReportActionID}`]; + if (!resolvedParentReportAction) { return false; } - if (parentReportAction.actionName !== CONST.REPORT.ACTIONS.TYPE.IOU) { + if (resolvedParentReportAction.actionName !== CONST.REPORT.ACTIONS.TYPE.IOU) { return false; } - const {IOUTransactionID, IOUReportID} = parentReportAction?.originalMessage; + const {IOUTransactionID, IOUReportID} = resolvedParentReportAction?.originalMessage; if (!IOUTransactionID || !IOUReportID) { return false; } @@ -3594,15 +3604,7 @@ function shouldReportBeInOptionList( } // Always show IOU reports with violations - if ( - isExpenseRequest(report) && - transactionThreadHasViolations( - report, - betas.includes(CONST.BETAS.VIOLATIONS), - transactionViolations, - allReportActions?.[`${report.parentReportID}`]?.[`${report.parentReportActionID}`], - ) - ) { + if (isExpenseRequest(report) && transactionThreadHasViolations(report, betas.includes(CONST.BETAS.VIOLATIONS), transactionViolations, allReportActions)) { return true; } diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index d550d899e078..3968f2f95547 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -343,7 +343,7 @@ function getOptionData( result.pendingAction = report.pendingFields ? report.pendingFields.addWorkspaceRoom || report.pendingFields.createChat : undefined; result.allReportErrors = OptionsListUtils.getAllReportErrors(report, reportActions) as OnyxCommon.Errors; result.brickRoadIndicator = - Object.keys(result.allReportErrors ?? {}).length !== 0 || ReportUtils.transactionThreadHasViolations(report, canUseViolations, transactionViolations, parentReportAction) + Object.keys(result.allReportErrors ?? {}).length !== 0 || ReportUtils.transactionThreadHasViolations(report, canUseViolations, transactionViolations, null, parentReportAction) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''; result.ownerAccountID = report.ownerAccountID; diff --git a/src/pages/home/sidebar/SidebarLinksData.js b/src/pages/home/sidebar/SidebarLinksData.js index a5788a91794a..cdf3140f174f 100644 --- a/src/pages/home/sidebar/SidebarLinksData.js +++ b/src/pages/home/sidebar/SidebarLinksData.js @@ -11,6 +11,7 @@ import withNavigationFocus from '@components/withNavigationFocus'; import useLocalize from '@hooks/useLocalize'; import compose from '@libs/compose'; import SidebarUtils from '@libs/SidebarUtils'; +import reportActionPropTypes from '@pages/home/report/reportActionPropTypes'; import reportPropTypes from '@pages/reportPropTypes'; import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; @@ -25,20 +26,9 @@ const propTypes = { chatReports: PropTypes.objectOf(reportPropTypes), /** All report actions for all reports */ - allReportActions: PropTypes.objectOf( - PropTypes.arrayOf( - PropTypes.shape({ - error: PropTypes.string, - message: PropTypes.arrayOf( - PropTypes.shape({ - moderationDecision: PropTypes.shape({ - decision: PropTypes.string, - }), - }), - ), - }), - ), - ), + + /** Object of report actions for this report */ + allReportActions: PropTypes.objectOf(PropTypes.shape(reportActionPropTypes)), /** Whether the reports are loading. When false it means they are ready to be used. */ isLoadingApp: PropTypes.bool, From da8f3769f3775ffa443f0880823de155f91d1dd9 Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Wed, 13 Dec 2023 09:55:59 -0500 Subject: [PATCH 037/161] Added usePermissions mock to ReportPreview --- tests/perf-test/ReportScreen.perf-test.js | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/perf-test/ReportScreen.perf-test.js b/tests/perf-test/ReportScreen.perf-test.js index 96514112cd05..8a5b35cb2f3a 100644 --- a/tests/perf-test/ReportScreen.perf-test.js +++ b/tests/perf-test/ReportScreen.perf-test.js @@ -56,6 +56,7 @@ jest.mock('../../src/hooks/useEnvironment', () => jest.mock('../../src/libs/Permissions', () => ({ canUseLinkPreviews: jest.fn(() => true), })); +jest.mock('../../src/hooks/usePermissions.ts'); jest.mock('../../src/libs/Navigation/Navigation'); From cda1aeeac983b13514baf4221c6157a0f8361ed5 Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Wed, 13 Dec 2023 12:22:50 -0500 Subject: [PATCH 038/161] Trying this out --- src/libs/SidebarUtils.ts | 63 +++++++++------------------------------- 1 file changed, 13 insertions(+), 50 deletions(-) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 3968f2f95547..5e41b48eab5b 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -108,55 +108,12 @@ function setWithLimit(map: Map, key: TKey, value: TV // Variable to verify if ONYX actions are loaded let hasInitialReportActions = false; -// Originally we were passing down the reportActions as the type OnyxCollection but -// it was used as a Record in the cachedReportsKey. This was causing a type error -// for the ReportUtils.shouldReportBeInOptionList function. To fix this, we are now passing down the reportActions -// as a Record and then converting it to a Record in the -// reportActionsSelector function. -// This function was originally in the parent component as an Onyx selector, but it was moved here -// to simplify the logic and prevent the need to pass down the reportActions as an array. - -type MappedReportAction = { - reportActionID: string; - errors: OnyxCommon.Errors; - message: Message[]; -}; - -type MappedReportActions = Record; - -const reportActionsSelector = (reportActionsCollection?: Record): Record | undefined => { - if (!reportActionsCollection) { - return undefined; +type ReportActionsCount = Record< + string, + { + reportActionsCount: number; } - - return Object.values(reportActionsCollection).reduce>((acc, reportActions) => { - if (!reportActions) { - return acc; - } - Object.entries(reportActions).forEach(([reportActionID, reportAction]) => { - const modifiedAction: MappedReportAction = { - reportActionID, - errors: reportAction.errors || [], - message: [ - { - moderationDecision: reportAction.message?.[0]?.moderationDecision, - type: reportAction.message?.[0]?.type, - text: reportAction.message?.[0]?.text, - }, - ], - }; - - // If the key already exists, append to the array, otherwise create a new array - if (acc[reportActionID]) { - acc[reportActionID].push(modifiedAction); - } else { - acc[reportActionID] = [modifiedAction]; - } - }); - - return acc; - }, {}); -}; +>; /** * @returns An array of reportIDs sorted in the proper order @@ -170,11 +127,17 @@ function getOrderedReportIDs( allReportActions: OnyxCollection, transactionViolations: TransactionViolations, ): string[] { - const mappedReportActions = allReportActions ? reportActionsSelector(allReportActions as unknown as Record) : {}; + const reportActionsCount: ReportActionsCount | null = + allReportActions && + Object.keys(allReportActions).reduce((acc, reportID) => { + acc[reportID] = {reportActionsCount: Object.keys(allReportActions[reportID] as Record).length}; + return acc; + }, {}); + // 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, mappedReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${currentReportId}`]?.length || 1], + [currentReportId, allReports, betas, policies, priorityMode, reportActionsCount?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${currentReportId}`]?.reportActionsCount || 1], (key, value: unknown) => { /** * Exclude 'participantAccountIDs', 'participants' and 'lastMessageText' not to overwhelm a cached key value with huge data, From cd6586a74297aab87b969e4a834566d3bd03e088 Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Wed, 13 Dec 2023 12:22:50 -0500 Subject: [PATCH 039/161] Trying this out --- src/libs/SidebarUtils.ts | 63 +++++------------------ tests/perf-test/ReportScreen.perf-test.js | 1 + 2 files changed, 14 insertions(+), 50 deletions(-) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 3968f2f95547..5e41b48eab5b 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -108,55 +108,12 @@ function setWithLimit(map: Map, key: TKey, value: TV // Variable to verify if ONYX actions are loaded let hasInitialReportActions = false; -// Originally we were passing down the reportActions as the type OnyxCollection but -// it was used as a Record in the cachedReportsKey. This was causing a type error -// for the ReportUtils.shouldReportBeInOptionList function. To fix this, we are now passing down the reportActions -// as a Record and then converting it to a Record in the -// reportActionsSelector function. -// This function was originally in the parent component as an Onyx selector, but it was moved here -// to simplify the logic and prevent the need to pass down the reportActions as an array. - -type MappedReportAction = { - reportActionID: string; - errors: OnyxCommon.Errors; - message: Message[]; -}; - -type MappedReportActions = Record; - -const reportActionsSelector = (reportActionsCollection?: Record): Record | undefined => { - if (!reportActionsCollection) { - return undefined; +type ReportActionsCount = Record< + string, + { + reportActionsCount: number; } - - return Object.values(reportActionsCollection).reduce>((acc, reportActions) => { - if (!reportActions) { - return acc; - } - Object.entries(reportActions).forEach(([reportActionID, reportAction]) => { - const modifiedAction: MappedReportAction = { - reportActionID, - errors: reportAction.errors || [], - message: [ - { - moderationDecision: reportAction.message?.[0]?.moderationDecision, - type: reportAction.message?.[0]?.type, - text: reportAction.message?.[0]?.text, - }, - ], - }; - - // If the key already exists, append to the array, otherwise create a new array - if (acc[reportActionID]) { - acc[reportActionID].push(modifiedAction); - } else { - acc[reportActionID] = [modifiedAction]; - } - }); - - return acc; - }, {}); -}; +>; /** * @returns An array of reportIDs sorted in the proper order @@ -170,11 +127,17 @@ function getOrderedReportIDs( allReportActions: OnyxCollection, transactionViolations: TransactionViolations, ): string[] { - const mappedReportActions = allReportActions ? reportActionsSelector(allReportActions as unknown as Record) : {}; + const reportActionsCount: ReportActionsCount | null = + allReportActions && + Object.keys(allReportActions).reduce((acc, reportID) => { + acc[reportID] = {reportActionsCount: Object.keys(allReportActions[reportID] as Record).length}; + return acc; + }, {}); + // 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, mappedReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${currentReportId}`]?.length || 1], + [currentReportId, allReports, betas, policies, priorityMode, reportActionsCount?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${currentReportId}`]?.reportActionsCount || 1], (key, value: unknown) => { /** * Exclude 'participantAccountIDs', 'participants' and 'lastMessageText' not to overwhelm a cached key value with huge data, diff --git a/tests/perf-test/ReportScreen.perf-test.js b/tests/perf-test/ReportScreen.perf-test.js index 8a5b35cb2f3a..830c23a613d7 100644 --- a/tests/perf-test/ReportScreen.perf-test.js +++ b/tests/perf-test/ReportScreen.perf-test.js @@ -55,6 +55,7 @@ jest.mock('../../src/hooks/useEnvironment', () => jest.mock('../../src/libs/Permissions', () => ({ canUseLinkPreviews: jest.fn(() => true), + canUseViolations: jest.fn(() => false), })); jest.mock('../../src/hooks/usePermissions.ts'); From 6478b9402536dfd73688d681ca02228300df4e74 Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Wed, 13 Dec 2023 13:59:01 -0500 Subject: [PATCH 040/161] fix for perf test --- src/components/ReportActionItem/ReportPreview.js | 2 +- tests/perf-test/ReportScreen.perf-test.js | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index 005f244cb6b1..be1973751a91 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -14,12 +14,12 @@ import {showContextMenuForReport} from '@components/ShowContextMenuContext'; import Text from '@components/Text'; import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import useLocalize from '@hooks/useLocalize'; +import {usePermissions} from '@hooks/usePermissions'; import compose from '@libs/compose'; import ControlSelection from '@libs/ControlSelection'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import Navigation from '@libs/Navigation/Navigation'; -import {usePermissions} from '@libs/Permissions'; import * as ReceiptUtils from '@libs/ReceiptUtils'; import * as ReportActionUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; diff --git a/tests/perf-test/ReportScreen.perf-test.js b/tests/perf-test/ReportScreen.perf-test.js index 830c23a613d7..8a5b35cb2f3a 100644 --- a/tests/perf-test/ReportScreen.perf-test.js +++ b/tests/perf-test/ReportScreen.perf-test.js @@ -55,7 +55,6 @@ jest.mock('../../src/hooks/useEnvironment', () => jest.mock('../../src/libs/Permissions', () => ({ canUseLinkPreviews: jest.fn(() => true), - canUseViolations: jest.fn(() => false), })); jest.mock('../../src/hooks/usePermissions.ts'); From 1736c2107ca865dc107effcd9ea1118bd4648075 Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Wed, 13 Dec 2023 14:03:17 -0500 Subject: [PATCH 041/161] Remove unused type --- src/libs/SidebarUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 5e41b48eab5b..2faf6c4b4170 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -9,7 +9,7 @@ import Beta from '@src/types/onyx/Beta'; import * as OnyxCommon from '@src/types/onyx/OnyxCommon'; import Policy from '@src/types/onyx/Policy'; import Report from '@src/types/onyx/Report'; -import ReportAction, {Message, ReportActions} from '@src/types/onyx/ReportAction'; +import ReportAction, {ReportActions} from '@src/types/onyx/ReportAction'; import * as CollectionUtils from './CollectionUtils'; import * as LocalePhoneNumber from './LocalePhoneNumber'; import * as Localize from './Localize'; From 16a0a9adf7e614b731a92c6fc775067c49aa1e70 Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Wed, 13 Dec 2023 14:52:33 -0500 Subject: [PATCH 042/161] Fixing some issues with tests --- src/libs/SidebarUtils.ts | 6 ++++-- tests/perf-test/ReportScreen.perf-test.js | 6 +++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 2faf6c4b4170..049652fbb254 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -129,8 +129,10 @@ function getOrderedReportIDs( ): string[] { const reportActionsCount: ReportActionsCount | null = allReportActions && - Object.keys(allReportActions).reduce((acc, reportID) => { - acc[reportID] = {reportActionsCount: Object.keys(allReportActions[reportID] as Record).length}; + Object.entries(allReportActions).reduce((acc, [reportID, reportActions]) => { + if (reportActions) { + acc[reportID] = {reportActionsCount: Object.keys(reportActions as Record).length}; + } return acc; }, {}); diff --git a/tests/perf-test/ReportScreen.perf-test.js b/tests/perf-test/ReportScreen.perf-test.js index 8a5b35cb2f3a..f5badad99ad3 100644 --- a/tests/perf-test/ReportScreen.perf-test.js +++ b/tests/perf-test/ReportScreen.perf-test.js @@ -56,7 +56,11 @@ jest.mock('../../src/hooks/useEnvironment', () => jest.mock('../../src/libs/Permissions', () => ({ canUseLinkPreviews: jest.fn(() => true), })); -jest.mock('../../src/hooks/usePermissions.ts'); +jest.mock('../../src/hooks/usePermissions.ts', () => + jest.fn(() => ({ + canUseViolations: true, + })), +); jest.mock('../../src/libs/Navigation/Navigation'); From feb359689af7fbd0791e792bcd2d7f113ffbf005 Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Wed, 13 Dec 2023 15:22:42 -0500 Subject: [PATCH 043/161] Was accidentally importing named instead of default --- src/components/ReportActionItem/ReportPreview.js | 2 +- tests/perf-test/ReportScreen.perf-test.js | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index be1973751a91..2a5f3e07de16 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -14,7 +14,7 @@ import {showContextMenuForReport} from '@components/ShowContextMenuContext'; import Text from '@components/Text'; import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import useLocalize from '@hooks/useLocalize'; -import {usePermissions} from '@hooks/usePermissions'; +import usePermissions from '@hooks/usePermissions'; import compose from '@libs/compose'; import ControlSelection from '@libs/ControlSelection'; import * as CurrencyUtils from '@libs/CurrencyUtils'; diff --git a/tests/perf-test/ReportScreen.perf-test.js b/tests/perf-test/ReportScreen.perf-test.js index f5badad99ad3..8a5b35cb2f3a 100644 --- a/tests/perf-test/ReportScreen.perf-test.js +++ b/tests/perf-test/ReportScreen.perf-test.js @@ -56,11 +56,7 @@ jest.mock('../../src/hooks/useEnvironment', () => jest.mock('../../src/libs/Permissions', () => ({ canUseLinkPreviews: jest.fn(() => true), })); -jest.mock('../../src/hooks/usePermissions.ts', () => - jest.fn(() => ({ - canUseViolations: true, - })), -); +jest.mock('../../src/hooks/usePermissions.ts'); jest.mock('../../src/libs/Navigation/Navigation'); From 69b767b8c998af1e1a099dfa487be1b29e4e5bba Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Wed, 13 Dec 2023 21:08:45 -0500 Subject: [PATCH 044/161] Update src/libs/ReportUtils.ts Co-authored-by: Carlos Alvarez --- src/libs/ReportUtils.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 08e2cd44dc67..3df5e24a4a1d 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3473,10 +3473,6 @@ function shouldHideReport(report: OnyxEntry, currentReportId: string): b return parentReport?.reportID !== report?.reportID && !isChildReportHasComment; } -/** - * Checks if there are any violations belonging to the transaction in the transactionsViolations Onyx object - * then checks that the violation is of the proper type - */ function transactionHasViolation(transactionID: string, transactionViolations?: TransactionViolations): boolean { const violations: TransactionViolation[] = transactionViolations ? transactionViolations[transactionID] || [] : []; if (!violations) { From 9535a2bbf7585a1ed334700a057d85d1d04545fb Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Wed, 13 Dec 2023 21:09:00 -0500 Subject: [PATCH 045/161] Update src/components/ReportActionItem/ReportPreview.js Co-authored-by: Carlos Alvarez --- src/components/ReportActionItem/ReportPreview.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/ReportActionItem/ReportPreview.js b/src/components/ReportActionItem/ReportPreview.js index 053fce6cfcd1..df012ca2f6cd 100644 --- a/src/components/ReportActionItem/ReportPreview.js +++ b/src/components/ReportActionItem/ReportPreview.js @@ -149,9 +149,7 @@ function ReportPreview(props) { const theme = useTheme(); const styles = useThemeStyles(); const {translate} = useLocalize(); - const {canUseViolations} = usePermissions(); - const managerID = props.iouReport.managerID || 0; const isCurrentUserManager = managerID === lodashGet(props.session, 'accountID'); const {totalDisplaySpend, reimbursableSpend} = ReportUtils.getMoneyRequestSpendBreakdown(props.iouReport); From 99d3c0d8e7d941e44ddf1715e99b4b9a406c6c09 Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Wed, 13 Dec 2023 21:09:10 -0500 Subject: [PATCH 046/161] Update src/hooks/__mocks__/usePermissions.ts Co-authored-by: Carlos Alvarez --- src/hooks/__mocks__/usePermissions.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/hooks/__mocks__/usePermissions.ts b/src/hooks/__mocks__/usePermissions.ts index 4af004095712..89a871e3c991 100644 --- a/src/hooks/__mocks__/usePermissions.ts +++ b/src/hooks/__mocks__/usePermissions.ts @@ -1,10 +1,8 @@ import UsePermissions from '@hooks/usePermissions'; /** - * * @returns {UsePermissions} A mock of the usePermissions hook. */ - const usePermissions = (): typeof UsePermissions => () => ({ canUseViolations: true, }); From 07209109f074649a167b12a9c82b1399cc31f2f2 Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Wed, 13 Dec 2023 21:38:57 -0500 Subject: [PATCH 047/161] Remove unused import --- src/libs/ReportUtils.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 3df5e24a4a1d..3ac9db07057b 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -37,7 +37,6 @@ import {Message, ReportActionBase, ReportActions} from '@src/types/onyx/ReportAc import {Receipt, WaypointCollection} from '@src/types/onyx/Transaction'; import DeepValueOf from '@src/types/utils/DeepValueOf'; import {EmptyObject, isEmptyObject, isNotEmptyObject} from '@src/types/utils/EmptyObject'; -// import * as CollectionUtils from './CollectionUtils'; import * as CurrencyUtils from './CurrencyUtils'; import DateUtils from './DateUtils'; import isReportMessageAttachment from './isReportMessageAttachment'; From 7faf50bcbef2a3ae13f4c8a11f2088d272104e0e Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Wed, 13 Dec 2023 21:48:14 -0500 Subject: [PATCH 048/161] Fix transactionHasViolation function and ReportActionsCount type --- src/libs/ReportUtils.ts | 6 +++--- src/libs/SidebarUtils.ts | 11 +++-------- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 3ac9db07057b..1a90a0d8aae0 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3472,8 +3472,8 @@ function shouldHideReport(report: OnyxEntry, currentReportId: string): b return parentReport?.reportID !== report?.reportID && !isChildReportHasComment; } -function transactionHasViolation(transactionID: string, transactionViolations?: TransactionViolations): boolean { - const violations: TransactionViolation[] = transactionViolations ? transactionViolations[transactionID] || [] : []; +function transactionHasViolation(transactionID: string, transactionViolations: TransactionViolations): boolean { + const violations: TransactionViolation[] = transactionViolations[transactionID]; if (!violations) { return false; } @@ -3488,7 +3488,7 @@ function transactionHasViolation(transactionID: string, transactionViolations?: function transactionThreadHasViolations( report: Report, canUseViolations: boolean, - transactionViolations?: TransactionViolations, + transactionViolations: TransactionViolations, reportActions?: OnyxCollection | null, parentReportAction?: ReportAction | null, ): boolean { diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 049652fbb254..78b9a3eae23a 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -108,12 +108,7 @@ function setWithLimit(map: Map, key: TKey, value: TV // Variable to verify if ONYX actions are loaded let hasInitialReportActions = false; -type ReportActionsCount = Record< - string, - { - reportActionsCount: number; - } ->; +type ReportActionsCount = Record; /** * @returns An array of reportIDs sorted in the proper order @@ -131,7 +126,7 @@ function getOrderedReportIDs( allReportActions && Object.entries(allReportActions).reduce((acc, [reportID, reportActions]) => { if (reportActions) { - acc[reportID] = {reportActionsCount: Object.keys(reportActions as Record).length}; + acc[reportID] = Object.keys(reportActions as Record).length; } return acc; }, {}); @@ -139,7 +134,7 @@ 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 - [currentReportId, allReports, betas, policies, priorityMode, reportActionsCount?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${currentReportId}`]?.reportActionsCount || 1], + [currentReportId, allReports, betas, policies, priorityMode, reportActionsCount?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${currentReportId}`] || 1], (key, value: unknown) => { /** * Exclude 'participantAccountIDs', 'participants' and 'lastMessageText' not to overwhelm a cached key value with huge data, From bf401afca2acaade3fff7c9b34ac7622dcc732b2 Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Wed, 13 Dec 2023 21:49:35 -0500 Subject: [PATCH 049/161] Remove unnecessary code in shouldHideReport function --- src/libs/ReportUtils.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 1a90a0d8aae0..daacc3fc1f41 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3474,9 +3474,6 @@ function shouldHideReport(report: OnyxEntry, currentReportId: string): b function transactionHasViolation(transactionID: string, transactionViolations: TransactionViolations): boolean { const violations: TransactionViolation[] = transactionViolations[transactionID]; - if (!violations) { - return false; - } return violations.some((violation: TransactionViolation) => violation.type === 'violation'); } From f0a360b40f066e0a996b4027ba84cbf9a44e08ee Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Wed, 13 Dec 2023 21:50:22 -0500 Subject: [PATCH 050/161] Remove unnecessary comment in ReportUtils.ts --- src/libs/ReportUtils.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index daacc3fc1f41..16ff2da4fc24 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3479,7 +3479,6 @@ function transactionHasViolation(transactionID: string, transactionViolations: T /** * Checks to see if a report's parentAction is a money request that contains a violation - * This only applies to report submitter and for reports in the open and processing states */ function transactionThreadHasViolations( From 4088e8f5651d54e260659ea396caa8c7d9b1c85a Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Wed, 13 Dec 2023 22:02:43 -0500 Subject: [PATCH 051/161] Fix betas check to be outside of the function --- src/libs/ReportUtils.ts | 5 ++--- src/libs/SidebarUtils.ts | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 16ff2da4fc24..ea33ee3123b2 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3483,12 +3483,11 @@ function transactionHasViolation(transactionID: string, transactionViolations: T function transactionThreadHasViolations( report: Report, - canUseViolations: boolean, transactionViolations: TransactionViolations, reportActions?: OnyxCollection | null, parentReportAction?: ReportAction | null, ): boolean { - if (!canUseViolations || !reportActions) { + if (!reportActions) { return false; } const resolvedParentReportAction = parentReportAction ?? reportActions?.[`${report.parentReportID}`]?.[`${report.parentReportActionID}`]; @@ -3595,7 +3594,7 @@ function shouldReportBeInOptionList( } // Always show IOU reports with violations - if (isExpenseRequest(report) && transactionThreadHasViolations(report, betas.includes(CONST.BETAS.VIOLATIONS), transactionViolations, allReportActions)) { + if (isExpenseRequest(report) && betas.includes(CONST.BETAS.VIOLATIONS) && transactionThreadHasViolations(report, transactionViolations, allReportActions)) { return true; } diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 78b9a3eae23a..ab7a822a01ea 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -303,7 +303,7 @@ function getOptionData( result.pendingAction = report.pendingFields ? report.pendingFields.addWorkspaceRoom || report.pendingFields.createChat : undefined; result.allReportErrors = OptionsListUtils.getAllReportErrors(report, reportActions) as OnyxCommon.Errors; result.brickRoadIndicator = - Object.keys(result.allReportErrors ?? {}).length !== 0 || ReportUtils.transactionThreadHasViolations(report, canUseViolations, transactionViolations, null, parentReportAction) + Object.keys(result.allReportErrors ?? {}).length !== 0 || (canUseViolations && ReportUtils.transactionThreadHasViolations(report, transactionViolations, null, parentReportAction)) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''; result.ownerAccountID = report.ownerAccountID; From 766d37e1068b98c46290925ae5febf126338d177 Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Wed, 13 Dec 2023 22:10:53 -0500 Subject: [PATCH 052/161] Add comment about using usePermissions hook mock for beta tests --- tests/unit/SidebarFilterTest.js | 2 +- tests/unit/SidebarOrderTest.js | 2 +- tests/unit/SidebarTest.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/unit/SidebarFilterTest.js b/tests/unit/SidebarFilterTest.js index 326ae6f76be2..8c16ada8154e 100644 --- a/tests/unit/SidebarFilterTest.js +++ b/tests/unit/SidebarFilterTest.js @@ -8,7 +8,7 @@ import * as LHNTestUtils from '../utils/LHNTestUtils'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; import wrapOnyxWithWaitForBatchedUpdates from '../utils/wrapOnyxWithWaitForBatchedUpdates'; -// Be sure to include the mocked permissions library or else the beta tests won't work +// Be sure to include the mocked permissions library as well as the usePermissions hook or else the beta tests won't work jest.mock('../../src/libs/Permissions'); jest.mock('../../src/hooks/usePermissions.ts'); diff --git a/tests/unit/SidebarOrderTest.js b/tests/unit/SidebarOrderTest.js index 55dcb7dd6258..b27fe9721e04 100644 --- a/tests/unit/SidebarOrderTest.js +++ b/tests/unit/SidebarOrderTest.js @@ -9,7 +9,7 @@ import * as LHNTestUtils from '../utils/LHNTestUtils'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; import wrapOnyxWithWaitForBatchedUpdates from '../utils/wrapOnyxWithWaitForBatchedUpdates'; -// Be sure to include the mocked Permissions and Expensicons libraries or else the beta tests won't work +// Be sure to include the mocked Permissions and Expensicons libraries as well as the usePermissions hook or else the beta tests won't work jest.mock('../../src/libs/Permissions'); jest.mock('../../src/hooks/usePermissions.ts'); jest.mock('../../src/components/Icon/Expensicons'); diff --git a/tests/unit/SidebarTest.js b/tests/unit/SidebarTest.js index 94ac50a6207e..78de26a4edef 100644 --- a/tests/unit/SidebarTest.js +++ b/tests/unit/SidebarTest.js @@ -7,7 +7,7 @@ import * as LHNTestUtils from '../utils/LHNTestUtils'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; import wrapOnyxWithWaitForBatchedUpdates from '../utils/wrapOnyxWithWaitForBatchedUpdates'; -// Be sure to include the mocked Permissions and Expensicons libraries or else the beta tests won't work +// Be sure to include the mocked Permissions and Expensicons libraries as well as the usePermissions hook or else the beta tests won't work jest.mock('../../src/libs/Permissions'); jest.mock('../../src/hooks/usePermissions.ts'); jest.mock('../../src/components/Icon/Expensicons'); From 561a4d4a2db715765a9f3815a15c5c63676f1513 Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Wed, 13 Dec 2023 22:21:42 -0500 Subject: [PATCH 053/161] Add default value for transactionViolations --- 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 ea33ee3123b2..46ae84718b14 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3594,7 +3594,7 @@ function shouldReportBeInOptionList( } // Always show IOU reports with violations - if (isExpenseRequest(report) && betas.includes(CONST.BETAS.VIOLATIONS) && transactionThreadHasViolations(report, transactionViolations, allReportActions)) { + if (isExpenseRequest(report) && betas.includes(CONST.BETAS.VIOLATIONS) && transactionThreadHasViolations(report, transactionViolations || {}, allReportActions)) { return true; } From abad008fe26bfb90e5ebf0fd193241e1a72c6771 Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Wed, 13 Dec 2023 22:25:41 -0500 Subject: [PATCH 054/161] Make linter happy --- src/hooks/__mocks__/usePermissions.ts | 6 ++---- src/libs/ReportUtils.ts | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/hooks/__mocks__/usePermissions.ts b/src/hooks/__mocks__/usePermissions.ts index 89a871e3c991..a12b1f52d8ab 100644 --- a/src/hooks/__mocks__/usePermissions.ts +++ b/src/hooks/__mocks__/usePermissions.ts @@ -1,9 +1,7 @@ -import UsePermissions from '@hooks/usePermissions'; - /** - * @returns {UsePermissions} A mock of the usePermissions hook. + * @returns A mock of the usePermissions hook. */ -const usePermissions = (): typeof UsePermissions => () => ({ +const usePermissions = () => ({ canUseViolations: true, }); export default usePermissions; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 46ae84718b14..5e6e208d7632 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3594,7 +3594,7 @@ function shouldReportBeInOptionList( } // Always show IOU reports with violations - if (isExpenseRequest(report) && betas.includes(CONST.BETAS.VIOLATIONS) && transactionThreadHasViolations(report, transactionViolations || {}, allReportActions)) { + if (isExpenseRequest(report) && betas.includes(CONST.BETAS.VIOLATIONS) && transactionThreadHasViolations(report, transactionViolations ?? {}, allReportActions)) { return true; } From 563ca7b62e094c805460198becbb16d8c7f5b9a9 Mon Sep 17 00:00:00 2001 From: Daniel Edwards Date: Wed, 13 Dec 2023 22:40:30 -0500 Subject: [PATCH 055/161] Refactor getOrderedReportIDs function to improve readability --- src/libs/SidebarUtils.ts | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index ab7a822a01ea..3f6b58020b27 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -108,8 +108,6 @@ function setWithLimit(map: Map, key: TKey, value: TV // Variable to verify if ONYX actions are loaded let hasInitialReportActions = false; -type ReportActionsCount = Record; - /** * @returns An array of reportIDs sorted in the proper order */ @@ -122,19 +120,19 @@ function getOrderedReportIDs( allReportActions: OnyxCollection, transactionViolations: TransactionViolations, ): string[] { - const reportActionsCount: ReportActionsCount | null = - allReportActions && - Object.entries(allReportActions).reduce((acc, [reportID, reportActions]) => { - if (reportActions) { - acc[reportID] = Object.keys(reportActions as Record).length; - } - return acc; - }, {}); + const countActions = (actions: ReportActions | null): number => (actions ? Object.keys(actions).length : 0); + + const getReportActionCount = (reportIDKey: string): number | null => { + if (!allReportActions?.[reportIDKey]) { + return null; + } + return countActions(allReportActions[reportIDKey]); + }; // 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, reportActionsCount?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${currentReportId}`] || 1], + [currentReportId, allReports, betas, policies, priorityMode, getReportActionCount(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${currentReportId}`) || 1], (key, value: unknown) => { /** * Exclude 'participantAccountIDs', 'participants' and 'lastMessageText' not to overwhelm a cached key value with huge data, From 2e5c39bdba1e5fa7212ab63c0b49c4bdaea3bdf8 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Thu, 14 Dec 2023 13:30:06 -0500 Subject: [PATCH 056/161] feat(Violations): implement types from money-request-preview branch --- src/CONST.ts | 38 ++++++++++++- src/libs/{ => Violations}/ViolationsUtils.ts | 0 src/libs/Violations/propTypes.ts | 32 +++++++++++ src/types/onyx/PolicyCategory.ts | 4 +- src/types/onyx/PolicyTag.ts | 3 +- src/types/onyx/TransactionViolation.ts | 60 ++++++++------------ src/types/onyx/index.ts | 4 +- tests/unit/ViolationUtilsTest.js | 2 +- 8 files changed, 99 insertions(+), 44 deletions(-) rename src/libs/{ => Violations}/ViolationsUtils.ts (100%) create mode 100644 src/libs/Violations/propTypes.ts diff --git a/src/CONST.ts b/src/CONST.ts index c0e3d64b5eee..4830b1f1dead 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -3018,12 +3018,48 @@ const CONST = { }, /** - * Constants for maxToRenderPerBatch parameter that is used for FlatList or SectionList. This controls the amount of items rendered per batch, which is the next chunk of items rendered on every scroll. + * Constants for maxToRenderPerBatch parameter that is used for FlatList or SectionList. This controls the amount + * of items rendered per batch, which is the next chunk of items rendered on every scroll. */ MAX_TO_RENDER_PER_BATCH: { DEFAULT: 5, CAROUSEL: 3, }, + VIOLATIONS: { + ALL_TAG_LEVELS_REQUIRED: 'allTagLevelsRequired', + AUTO_REPORTED_REJECTED_EXPENSE: 'autoReportedRejectedExpense', + BILLABLE_EXPENSE: 'billableExpense', + CASH_EXPENSE_WITH_NO_RECEIPT: 'cashExpenseWithNoReceipt', + CATEGORY_OUT_OF_POLICY: 'categoryOutOfPolicy', + CONVERSION_SURCHARGE: 'conversionSurcharge', + CUSTOM_UNIT_OUT_OF_POLICY: 'customUnitOutOfPolicy', + DUPLICATED_TRANSACTION: 'duplicatedTransaction', + FIELD_REQUIRED: 'fieldRequired', + FUTURE_DATE: 'futureDate', + INVOICE_MARKUP: 'invoiceMarkup', + MAX_AGE: 'maxAge', + MISSING_CATEGORY: 'missingCategory', + MISSING_COMMENT: 'missingComment', + MISSING_TAG: 'missingTag', + MODIFIED_AMOUNT: 'modifiedAmount', + MODIFIED_DATE: 'modifiedDate', + NON_EXPENSIWORKS_EXPENSE: 'nonExpensiworksExpense', + OVER_AUTO_APPROVAL_LIMIT: 'overAutoApprovalLimit', + OVER_CATEGORY_LIMIT: 'overCategoryLimit', + OVER_LIMIT: 'overLimit', + OVER_LIMIT_ATTENDEE: 'overLimitAttendee', + PER_DAY_LIMIT: 'perDayLimit', + RECEIPT_NOT_SMART_SCANNED: 'receiptNotSmartScanned', + RECEIPT_REQUIRED: 'receiptRequired', + RTER: 'rter', + SMARTSCAN_FAILED: 'smartscanFailed', + SOME_TAG_LEVELS_REQUIRED: 'someTagLevelsRequired', + TAG_OUT_OF_POLICY: 'tagOutOfPolicy', + TAX_AMOUNT_CHANGED: 'taxAmountChanged', + TAX_OUT_OF_POLICY: 'taxOutOfPolicy', + TAX_RATE_CHANGED: 'taxRateChanged', + TAX_REQUIRED: 'taxRequired', + }, } as const; export default CONST; diff --git a/src/libs/ViolationsUtils.ts b/src/libs/Violations/ViolationsUtils.ts similarity index 100% rename from src/libs/ViolationsUtils.ts rename to src/libs/Violations/ViolationsUtils.ts diff --git a/src/libs/Violations/propTypes.ts b/src/libs/Violations/propTypes.ts new file mode 100644 index 000000000000..e32d9ddd6a2c --- /dev/null +++ b/src/libs/Violations/propTypes.ts @@ -0,0 +1,32 @@ +import PropTypes from 'prop-types'; +import CONST from '@src/CONST'; + +const violationNames = Object.values(CONST.VIOLATIONS); + +const transactionViolationPropType = PropTypes.shape({ + type: PropTypes.string.isRequired, + name: PropTypes.oneOf(violationNames).isRequired, + data: PropTypes.shape({ + rejectedBy: PropTypes.string, + rejectReason: PropTypes.string, + amount: PropTypes.string, + surcharge: PropTypes.number, + invoiceMarkup: PropTypes.number, + maxAge: PropTypes.number, + tagName: PropTypes.string, + formattedLimitAmount: PropTypes.string, + categoryLimit: PropTypes.string, + limit: PropTypes.string, + category: PropTypes.string, + brokenBankConnection: PropTypes.bool, + isAdmin: PropTypes.bool, + email: PropTypes.string, + isTransactionOlderThan7Days: PropTypes.bool, + member: PropTypes.string, + taxName: PropTypes.string, + }), +}); + +const transactionViolationsPropType = PropTypes.arrayOf(transactionViolationPropType); + +export {transactionViolationsPropType, transactionViolationPropType}; diff --git a/src/types/onyx/PolicyCategory.ts b/src/types/onyx/PolicyCategory.ts index b6dfb7bbab9a..03d5877bc5b5 100644 --- a/src/types/onyx/PolicyCategory.ts +++ b/src/types/onyx/PolicyCategory.ts @@ -20,5 +20,5 @@ type PolicyCategory = { }; type PolicyCategories = Record; -export default PolicyCategory; -export type {PolicyCategories}; + +export type {PolicyCategory, PolicyCategories}; diff --git a/src/types/onyx/PolicyTag.ts b/src/types/onyx/PolicyTag.ts index 7807dcc00433..58a21dcf4df5 100644 --- a/src/types/onyx/PolicyTag.ts +++ b/src/types/onyx/PolicyTag.ts @@ -12,5 +12,4 @@ type PolicyTag = { type PolicyTags = Record; -export default PolicyTag; -export type {PolicyTags}; +export type {PolicyTag, PolicyTags}; diff --git a/src/types/onyx/TransactionViolation.ts b/src/types/onyx/TransactionViolation.ts index bd8f3059caea..1d02de12a52a 100644 --- a/src/types/onyx/TransactionViolation.ts +++ b/src/types/onyx/TransactionViolation.ts @@ -1,46 +1,34 @@ +import CONST from '@src/CONST'; + /** - * Names of transaction violations + * Names of violations. + * Derived from `CONST.VIOLATIONS` to maintain a single source of truth. */ -type ViolationName = - | 'allTagLevelsRequired' - | 'autoReportedRejectedExpense' - | 'billableExpense' - | 'cashExpenseWithNoReceipt' - | 'categoryOutOfPolicy' - | 'conversionSurcharge' - | 'customUnitOutOfPolicy' - | 'duplicatedTransaction' - | 'fieldRequired' - | 'futureDate' - | 'invoiceMarkup' - | 'maxAge' - | 'missingCategory' - | 'missingComment' - | 'missingTag' - | 'modifiedAmount' - | 'modifiedDate' - | 'nonExpensiworksExpense' - | 'overAutoApprovalLimit' - | 'overCategoryLimit' - | 'overLimit' - | 'overLimitAttendee' - | 'perDayLimit' - | 'receiptNotSmartScanned' - | 'receiptRequired' - | 'rter' - | 'smartscanFailed' - | 'someTagLevelsRequired' - | 'tagOutOfPolicy' - | 'taxAmountChanged' - | 'taxOutOfPolicy' - | 'taxRateChanged' - | 'taxRequired'; +type ViolationName = (typeof CONST.VIOLATIONS)[keyof typeof CONST.VIOLATIONS]; type TransactionViolation = { type: string; name: ViolationName; userMessage: string; - data?: Record; + data?: { + rejectedBy?: string; + rejectReason?: string; + amount?: string; + surcharge?: number; + invoiceMarkup?: number; + maxAge?: number; + tagName?: string; + formattedLimitAmount?: string; + categoryLimit?: string; + limit?: string; + category?: string; + brokenBankConnection?: boolean; + isAdmin?: boolean; + email?: string; + isTransactionOlderThan7Days?: boolean; + member?: string; + taxName?: string; + }; }; type TransactionViolations = Record; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index b52a9c919c39..23e2eb1f3bcd 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -23,9 +23,9 @@ import PersonalBankAccount from './PersonalBankAccount'; import PersonalDetails, {PersonalDetailsList} from './PersonalDetails'; import PlaidData from './PlaidData'; import Policy from './Policy'; -import PolicyCategory, {PolicyCategories} from './PolicyCategory'; +import {PolicyCategories, PolicyCategory} from './PolicyCategory'; import PolicyMember, {PolicyMembers} from './PolicyMember'; -import PolicyTag, {PolicyTags} from './PolicyTag'; +import {PolicyTag, PolicyTags} from './PolicyTag'; import PrivatePersonalDetails from './PrivatePersonalDetails'; import RecentlyUsedCategories from './RecentlyUsedCategories'; import RecentlyUsedTags from './RecentlyUsedTags'; diff --git a/tests/unit/ViolationUtilsTest.js b/tests/unit/ViolationUtilsTest.js index cc84c547da2e..f0b53443831e 100644 --- a/tests/unit/ViolationUtilsTest.js +++ b/tests/unit/ViolationUtilsTest.js @@ -1,6 +1,6 @@ import {beforeEach} from '@jest/globals'; import Onyx from 'react-native-onyx'; -import ViolationsUtils from '@libs/ViolationsUtils'; +import ViolationsUtils from '@libs/Violations/ViolationsUtils'; import ONYXKEYS from '@src/ONYXKEYS'; const categoryOutOfPolicyViolation = { From 30a77c92d3d05272f193ba856df513e53189f99c Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Thu, 14 Dec 2023 13:31:20 -0500 Subject: [PATCH 057/161] feat(Violations): replace transactionViolationsProp with imported prop from libs/Violations --- .../LHNOptionsList/LHNOptionsList.js | 25 ++----------------- 1 file changed, 2 insertions(+), 23 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.js b/src/components/LHNOptionsList/LHNOptionsList.js index 87bff1667c3f..ab6da0afa1c8 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.js +++ b/src/components/LHNOptionsList/LHNOptionsList.js @@ -13,6 +13,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import compose from '@libs/compose'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as ReportUtils from '@libs/ReportUtils'; +import {transactionViolationsPropType} from '@libs/Violations/propTypes'; import reportActionPropTypes from '@pages/home/report/reportActionPropTypes'; import reportPropTypes from '@pages/reportPropTypes'; import stylePropTypes from '@styles/stylePropTypes'; @@ -68,29 +69,7 @@ const propTypes = { draftComments: PropTypes.objectOf(PropTypes.string), /** The list of transaction violations */ - transactionViolations: PropTypes.shape({ - violations: PropTypes.arrayOf( - PropTypes.shape({ - /** The transaction ID */ - transactionID: PropTypes.number, - - /** The transaction violation type */ - type: PropTypes.string, - - /** The transaction violation message */ - message: PropTypes.string, - - /** The transaction violation data */ - data: PropTypes.shape({ - /** The transaction violation data field */ - field: PropTypes.string, - - /** The transaction violation data value */ - value: PropTypes.string, - }), - }), - ), - }), + transactionViolations: transactionViolationsPropType, ...withCurrentReportIDPropTypes, }; From f34a723a5c345ca95d6cff4f3ed512dac73b94ed Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Thu, 14 Dec 2023 20:03:26 +0100 Subject: [PATCH 058/161] fix: linter --- src/types/onyx/ReportAction.ts | 42 ---------------------------------- 1 file changed, 42 deletions(-) diff --git a/src/types/onyx/ReportAction.ts b/src/types/onyx/ReportAction.ts index 5131e2290d95..8e56aaa67345 100644 --- a/src/types/onyx/ReportAction.ts +++ b/src/types/onyx/ReportAction.ts @@ -7,48 +7,6 @@ import OriginalMessage, {Decision, Reaction} from './OriginalMessage'; import {NotificationPreference} from './Report'; import {Receipt} from './Transaction'; -type Image = { - /** The height of the image. */ - height?: number; - - /** The width of the image. */ - width?: number; - - /** The URL of the image. */ - url?: string; -}; - -type Logo = { - /** The height of the logo. */ - height?: number; - - /** The width of the logo. */ - width?: number; - - /** The URL of the logo. */ - url?: string; -}; - -type LinkMetaData = { - /** The URL of the link. */ - url?: string; - - /** A description of the link. */ - description?: string; - - /** The title of the link. */ - title?: string; - - /** The publisher of the link. */ - publisher?: string; - - /** The image associated with the link. */ - image: Image; - - /** The provider logo associated with the link. */ - logo: Logo; -}; - type Message = { /** The type of the action item fragment. Used to render a corresponding component */ type: string; From 2e3d3e751274fee7da86998745b5432963d9735a Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Thu, 14 Dec 2023 14:19:37 -0500 Subject: [PATCH 059/161] feat(Violations): make reportActions mandatory and remove parentReportAction --- src/libs/ReportUtils.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 96bd16f03e9e..164feac59b8c 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3485,17 +3485,11 @@ function transactionHasViolation(transactionID: string, transactionViolations: T /** * Checks to see if a report's parentAction is a money request that contains a violation */ - -function transactionThreadHasViolations( - report: Report, - transactionViolations: TransactionViolations, - reportActions?: OnyxCollection | null, - parentReportAction?: ReportAction | null, -): boolean { +function transactionThreadHasViolations(report: Report, transactionViolations: TransactionViolations, reportActions: OnyxCollection | null = {}): boolean { if (!reportActions) { return false; } - const resolvedParentReportAction = parentReportAction ?? reportActions?.[`${report.parentReportID}`]?.[`${report.parentReportActionID}`]; + const resolvedParentReportAction = reportActions[`${report.parentReportID}`]?.[`${report.parentReportActionID}`]; if (!resolvedParentReportAction) { return false; } From 9503748199f93349acd0a3c70dfd059345b89666 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Thu, 14 Dec 2023 14:19:48 -0500 Subject: [PATCH 060/161] feat(Violations): remove whitespace --- src/libs/ReportUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 164feac59b8c..357404e8283c 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3483,7 +3483,7 @@ function transactionHasViolation(transactionID: string, transactionViolations: T } /** - * Checks to see if a report's parentAction is a money request that contains a violation + * Checks to see if a report's parentAction is a money request that contains a violation */ function transactionThreadHasViolations(report: Report, transactionViolations: TransactionViolations, reportActions: OnyxCollection | null = {}): boolean { if (!reportActions) { @@ -3510,7 +3510,7 @@ function transactionThreadHasViolations(report: Report, transactionViolations: T } /** - * Checks to see if a report contains a violation + * Checks to see if a report contains a violation */ function reportHasViolations(reportID: string, transactionViolations: TransactionViolations): boolean { const transactions = TransactionUtils.getAllReportTransactions(reportID); From 2871cd833d1c5d5c2c8c906e446f8b50e2702d7c Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Thu, 14 Dec 2023 15:10:21 -0500 Subject: [PATCH 061/161] feat(Violations): simplify --- src/libs/SidebarUtils.ts | 37 ++++++++++++------------------------- 1 file changed, 12 insertions(+), 25 deletions(-) diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 3f6b58020b27..6a5089d4de55 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -120,30 +120,17 @@ function getOrderedReportIDs( allReportActions: OnyxCollection, transactionViolations: TransactionViolations, ): string[] { - const countActions = (actions: ReportActions | null): number => (actions ? Object.keys(actions).length : 0); - - const getReportActionCount = (reportIDKey: string): number | null => { - if (!allReportActions?.[reportIDKey]) { - return null; - } - return countActions(allReportActions[reportIDKey]); - }; + const reportIDKey = `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${currentReportId}`; + const reportActionCount = allReportActions?.[reportIDKey]?.length ?? 1; // 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, getReportActionCount(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${currentReportId}`) || 1], - (key, value: unknown) => { - /** - * Exclude 'participantAccountIDs', 'participants' and 'lastMessageText' 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') { - return undefined; - } - - return value; - }, + [currentReportId, allReports, betas, policies, priorityMode, reportActionCount], + /** + * Exclude 'participantAccountIDs', 'participants' and 'lastMessageText' keys from the stringified + * objects to keep the size of the resulting key manageable. + */ + (key, value: unknown) => (['participantAccountIDs', 'participants', 'lastMessageText'].includes(key) ? undefined : value), ); // Check if the result is already in the cache @@ -286,8 +273,11 @@ function getOptionData( isWaitingOnBankAccount: false, isAllowedToComment: true, }; + const participantPersonalDetailList: PersonalDetails[] = Object.values(OptionsListUtils.getPersonalDetailsForAccountIDs(report.participantAccountIDs ?? [], personalDetails)); const personalDetail = participantPersonalDetailList[0] ?? {}; + const hasErrors = Object.keys(result.allReportErrors ?? {}).length !== 0; + const hasViolations = canUseViolations && ReportUtils.transactionThreadHasViolations(report, transactionViolations, null); result.isThread = ReportUtils.isChatThread(report); result.isChatRoom = ReportUtils.isChatRoom(report); @@ -300,10 +290,7 @@ function getOptionData( result.shouldShowSubscript = ReportUtils.shouldReportShowSubscript(report); result.pendingAction = report.pendingFields ? report.pendingFields.addWorkspaceRoom || report.pendingFields.createChat : undefined; result.allReportErrors = OptionsListUtils.getAllReportErrors(report, reportActions) as OnyxCommon.Errors; - result.brickRoadIndicator = - Object.keys(result.allReportErrors ?? {}).length !== 0 || (canUseViolations && ReportUtils.transactionThreadHasViolations(report, transactionViolations, null, parentReportAction)) - ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR - : ''; + result.brickRoadIndicator = hasErrors || hasViolations ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''; result.ownerAccountID = report.ownerAccountID; result.managerID = report.managerID; result.reportID = report.reportID; From 5dd7d01072bfacc86b1d898c599b0d204dd6ec8f Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Thu, 14 Dec 2023 15:10:33 -0500 Subject: [PATCH 062/161] feat(Violations): update comment --- tests/unit/SidebarFilterTest.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/unit/SidebarFilterTest.js b/tests/unit/SidebarFilterTest.js index 8c16ada8154e..7ae49e2eab9e 100644 --- a/tests/unit/SidebarFilterTest.js +++ b/tests/unit/SidebarFilterTest.js @@ -8,7 +8,8 @@ import * as LHNTestUtils from '../utils/LHNTestUtils'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; import wrapOnyxWithWaitForBatchedUpdates from '../utils/wrapOnyxWithWaitForBatchedUpdates'; -// Be sure to include the mocked permissions library as well as the usePermissions hook or else the beta tests won't work +// Be sure to include the mocked permissions library, as some components that are rendered +// during the test depend on its methods. jest.mock('../../src/libs/Permissions'); jest.mock('../../src/hooks/usePermissions.ts'); From d169a435d8048fcbbeece60fb74ba08cc6ea54e2 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Thu, 14 Dec 2023 15:17:06 -0500 Subject: [PATCH 063/161] feat(Violations): simplify feat(Violations): simplify feat(Violations): simplify feat(Violations): simplify feat(Violations): simplify feat(Violations): simplify feat(Violations): simplify feat(Violations): simplify --- src/libs/ReportUtils.ts | 65 +++++++++++------------------------------ 1 file changed, 17 insertions(+), 48 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 357404e8283c..d4fd2cc80960 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -483,9 +483,7 @@ function getPolicyName(report: OnyxEntry | undefined | EmptyObject, retu // Public rooms send back the policy name with the reportSummary, // since they can also be accessed by people who aren't in the workspace // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - const policyName = finalPolicy?.name || report?.policyName || report?.oldPolicyName || noPolicyFound; - - return policyName; + return finalPolicy?.name || report?.policyName || report?.oldPolicyName || noPolicyFound; } /** @@ -536,11 +534,7 @@ function isCanceledTaskReport(report: OnyxEntry | EmptyObject = {}, pare return true; } - if (isNotEmptyObject(report) && report?.isDeletedParentAction) { - return true; - } - - return false; + return Boolean(isNotEmptyObject(report) && report?.isDeletedParentAction); } /** @@ -1176,8 +1170,7 @@ function getReportRecipientAccountIDs(report: OnyxEntry, currentLoginAcc } const reportParticipants = finalParticipantAccountIDs?.filter((accountID) => accountID !== currentLoginAccountID) ?? []; - const participantsWithoutExpensifyAccountIDs = reportParticipants.filter((participant) => !CONST.EXPENSIFY_ACCOUNT_IDS.includes(participant ?? 0)); - return participantsWithoutExpensifyAccountIDs; + return reportParticipants.filter((participant) => !CONST.EXPENSIFY_ACCOUNT_IDS.includes(participant ?? 0)); } /** @@ -1281,13 +1274,12 @@ function getWorkspaceIcon(report: OnyxEntry, policy: OnyxEntry = ? allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`]?.avatar : getDefaultWorkspaceAvatar(workspaceName); - const workspaceIcon: Icon = { + return { source: policyExpenseChatAvatarSource ?? '', type: CONST.ICON_TYPE_WORKSPACE, name: workspaceName, id: -1, - }; - return workspaceIcon; + } as Icon; } /** @@ -1625,12 +1617,8 @@ function requiresAttentionFromCurrentUser(optionOrReport: OnyxEntry | Op return true; } - // Has a child report that is awaiting action (e.g. approve, pay, add bank account) from current user - if (optionOrReport.hasOutstandingChildRequest) { - return true; - } - - return false; + // has a child report that is awaiting action (e.g. approve, pay, add bank account) from current user + return Boolean(optionOrReport.hasOutstandingChildRequest); } /** @@ -3460,11 +3448,7 @@ function canAccessReport(report: OnyxEntry, policies: OnyxCollection { const sortedNewParticipantList = newParticipantList.sort(); @@ -3719,7 +3704,8 @@ function shouldShowFlagComment(reportAction: OnyxEntry, report: On } /** - * @param sortedAndFilteredReportActions - reportActions for the report, sorted newest to oldest, and filtered for only those that should be visible + * @param sortedAndFilteredReportActions - reportActions for the report, sorted newest to oldest, and filtered for only + * those that should be visible */ function getNewMarkerReportActionID(report: OnyxEntry, sortedAndFilteredReportActions: ReportAction[]): string { if (!isUnread(report)) { @@ -3988,19 +3974,7 @@ function shouldReportShowSubscript(report: OnyxEntry): boolean { return true; } - if (isExpenseRequest(report)) { - return true; - } - - if (isWorkspaceTaskReport(report)) { - return true; - } - - if (isWorkspaceThread(report)) { - return true; - } - - return false; + return isExpenseRequest(report) || isWorkspaceTaskReport(report) || isWorkspaceThread(report); } /** @@ -4236,8 +4210,8 @@ function getParticipantsIDs(report: OnyxEntry): number[] { // Build participants list for IOU/expense reports if (isMoneyRequestReport(report)) { const onlyTruthyValues = [report.managerID, report.ownerAccountID, ...participants].filter(Boolean) as number[]; - const onlyUnique = [...new Set([...onlyTruthyValues])]; - return onlyUnique; + // return only unique values + return [...new Set([...onlyTruthyValues])]; } return participants; } @@ -4327,8 +4301,7 @@ function shouldUseFullTitleToDisplay(report: OnyxEntry): boolean { } function getRoom(type: ValueOf, policyID: string): OnyxEntry | undefined { - const room = Object.values(allReports ?? {}).find((report) => report?.policyID === policyID && report?.chatType === type && !isThread(report)); - return room; + return Object.values(allReports ?? {}).find((report) => report?.policyID === policyID && report?.chatType === type && !isThread(report)); } /** @@ -4364,11 +4337,7 @@ function shouldAutoFocusOnKeyPress(event: KeyboardEvent): boolean { return false; } - if (event.code === 'Space') { - return false; - } - - return true; + return event.code !== 'Space'; } /** From aa7822e75831f25cc1cd9d6ac0c9365edfcba90a Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Thu, 14 Dec 2023 16:05:32 -0500 Subject: [PATCH 064/161] feat(Violations): fix type of transactionViolations propType --- src/components/LHNOptionsList/LHNOptionsList.js | 3 ++- src/components/LHNOptionsList/OptionRowLHNData.js | 4 ++++ src/libs/Violations/propTypes.ts | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.js b/src/components/LHNOptionsList/LHNOptionsList.js index ab6da0afa1c8..8b2384c4dec8 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.js +++ b/src/components/LHNOptionsList/LHNOptionsList.js @@ -65,6 +65,7 @@ const propTypes = { /** The transaction from the parent report action */ transactions: PropTypes.objectOf(transactionPropTypes), + /** List of draft comments */ draftComments: PropTypes.objectOf(PropTypes.string), @@ -214,7 +215,7 @@ export default compose( key: ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, }, transactionViolations: { - key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, + key: ({currentReportId}) => `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${currentReportId}`, }, }), )(LHNOptionsList); diff --git a/src/components/LHNOptionsList/OptionRowLHNData.js b/src/components/LHNOptionsList/OptionRowLHNData.js index 975299bb8800..82ec30cafe77 100644 --- a/src/components/LHNOptionsList/OptionRowLHNData.js +++ b/src/components/LHNOptionsList/OptionRowLHNData.js @@ -7,6 +7,7 @@ import transactionPropTypes from '@components/transactionPropTypes'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import SidebarUtils from '@libs/SidebarUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; +import {transactionViolationsPropType} from '@libs/Violations/propTypes'; import reportActionPropTypes from '@pages/home/report/reportActionPropTypes'; import * as Report from '@userActions/Report'; import CONST from '@src/CONST'; @@ -42,6 +43,9 @@ const propTypes = { /** The transaction from the parent report action */ transaction: transactionPropTypes, + /** Any violations associated with the report */ + transactionViolations: transactionViolationsPropType, + ...basePropTypes, }; diff --git a/src/libs/Violations/propTypes.ts b/src/libs/Violations/propTypes.ts index e32d9ddd6a2c..f0b84cf7d3b1 100644 --- a/src/libs/Violations/propTypes.ts +++ b/src/libs/Violations/propTypes.ts @@ -27,6 +27,6 @@ const transactionViolationPropType = PropTypes.shape({ }), }); -const transactionViolationsPropType = PropTypes.arrayOf(transactionViolationPropType); +const transactionViolationsPropType = PropTypes.objectOf(transactionViolationPropType); export {transactionViolationsPropType, transactionViolationPropType}; From d86616def9715f7511617cb97c40daf722109897 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Fri, 15 Dec 2023 10:37:35 -0500 Subject: [PATCH 065/161] feat(Violations): revert simplifications --- src/libs/ReportUtils.ts | 59 ++++++++++++++++++++++++++++++++--------- 1 file changed, 46 insertions(+), 13 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index d4fd2cc80960..177718c2c36c 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -483,7 +483,9 @@ function getPolicyName(report: OnyxEntry | undefined | EmptyObject, retu // Public rooms send back the policy name with the reportSummary, // since they can also be accessed by people who aren't in the workspace // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - return finalPolicy?.name || report?.policyName || report?.oldPolicyName || noPolicyFound; + const policyName = finalPolicy?.name || report?.policyName || report?.oldPolicyName || noPolicyFound; + + return policyName; } /** @@ -534,7 +536,11 @@ function isCanceledTaskReport(report: OnyxEntry | EmptyObject = {}, pare return true; } - return Boolean(isNotEmptyObject(report) && report?.isDeletedParentAction); + if (isNotEmptyObject(report) && report?.isDeletedParentAction) { + return true; + } + + return false; } /** @@ -1170,7 +1176,8 @@ function getReportRecipientAccountIDs(report: OnyxEntry, currentLoginAcc } const reportParticipants = finalParticipantAccountIDs?.filter((accountID) => accountID !== currentLoginAccountID) ?? []; - return reportParticipants.filter((participant) => !CONST.EXPENSIFY_ACCOUNT_IDS.includes(participant ?? 0)); + const participantsWithoutExpensifyAccountIDs = reportParticipants.filter((participant) => !CONST.EXPENSIFY_ACCOUNT_IDS.includes(participant ?? 0)); + return participantsWithoutExpensifyAccountIDs; } /** @@ -1274,12 +1281,13 @@ function getWorkspaceIcon(report: OnyxEntry, policy: OnyxEntry = ? allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`]?.avatar : getDefaultWorkspaceAvatar(workspaceName); - return { + const workspaceIcon: Icon = { source: policyExpenseChatAvatarSource ?? '', type: CONST.ICON_TYPE_WORKSPACE, name: workspaceName, id: -1, - } as Icon; + }; + return workspaceIcon; } /** @@ -1617,8 +1625,12 @@ function requiresAttentionFromCurrentUser(optionOrReport: OnyxEntry | Op return true; } - // has a child report that is awaiting action (e.g. approve, pay, add bank account) from current user - return Boolean(optionOrReport.hasOutstandingChildRequest); + // Has a child report that is awaiting action (e.g. approve, pay, add bank account) from current user + if (optionOrReport.hasOutstandingChildRequest) { + return true; + } + + return false; } /** @@ -3448,7 +3460,11 @@ function canAccessReport(report: OnyxEntry, policies: OnyxCollection): boolean { return true; } - return isExpenseRequest(report) || isWorkspaceTaskReport(report) || isWorkspaceThread(report); + if (isExpenseRequest(report)) { + return true; + } + + if (isWorkspaceTaskReport(report)) { + return true; + } + + if (isWorkspaceThread(report)) { + return true; + } + + return false; } /** @@ -4210,8 +4238,8 @@ function getParticipantsIDs(report: OnyxEntry): number[] { // Build participants list for IOU/expense reports if (isMoneyRequestReport(report)) { const onlyTruthyValues = [report.managerID, report.ownerAccountID, ...participants].filter(Boolean) as number[]; - // return only unique values - return [...new Set([...onlyTruthyValues])]; + const onlyUnique = [...new Set([...onlyTruthyValues])]; + return onlyUnique; } return participants; } @@ -4301,7 +4329,8 @@ function shouldUseFullTitleToDisplay(report: OnyxEntry): boolean { } function getRoom(type: ValueOf, policyID: string): OnyxEntry | undefined { - return Object.values(allReports ?? {}).find((report) => report?.policyID === policyID && report?.chatType === type && !isThread(report)); + const room = Object.values(allReports ?? {}).find((report) => report?.policyID === policyID && report?.chatType === type && !isThread(report)); + return room; } /** @@ -4337,7 +4366,11 @@ function shouldAutoFocusOnKeyPress(event: KeyboardEvent): boolean { return false; } - return event.code !== 'Space'; + if (event.code === 'Space') { + return false; + } + + return true; } /** From ccb12ccdc8e7883de6a14fe76b5ce50f667bbe52 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Fri, 15 Dec 2023 10:48:28 -0500 Subject: [PATCH 066/161] feat(Violations): add second withOnyx --- src/components/LHNOptionsList/LHNOptionsList.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.js b/src/components/LHNOptionsList/LHNOptionsList.js index 8b2384c4dec8..2999cd9ebda5 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.js +++ b/src/components/LHNOptionsList/LHNOptionsList.js @@ -1,5 +1,6 @@ import {FlashList} from '@shopify/flash-list'; import lodashGet from 'lodash/get'; +import lodashMap from 'lodash/map'; import PropTypes from 'prop-types'; import React, {useCallback} from 'react'; import {View} from 'react-native'; @@ -214,8 +215,10 @@ export default compose( draftComments: { key: ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, }, + }), + withOnyx({ transactionViolations: { - key: ({currentReportId}) => `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${currentReportId}`, + key: ({transactions}) => lodashMap(transactions, (t) => `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${t.transactionID}`), }, }), )(LHNOptionsList); From e3d4ae85c83554a7e493806c2acbd7fbe8ee0aff Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Fri, 15 Dec 2023 16:29:42 -0500 Subject: [PATCH 067/161] feat(Violations): get transactions directly --- src/components/LHNOptionsList/LHNOptionsList.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/components/LHNOptionsList/LHNOptionsList.js b/src/components/LHNOptionsList/LHNOptionsList.js index 2999cd9ebda5..1dfb70f65847 100644 --- a/src/components/LHNOptionsList/LHNOptionsList.js +++ b/src/components/LHNOptionsList/LHNOptionsList.js @@ -1,6 +1,5 @@ import {FlashList} from '@shopify/flash-list'; import lodashGet from 'lodash/get'; -import lodashMap from 'lodash/map'; import PropTypes from 'prop-types'; import React, {useCallback} from 'react'; import {View} from 'react-native'; @@ -215,10 +214,8 @@ export default compose( draftComments: { key: ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT, }, - }), - withOnyx({ transactionViolations: { - key: ({transactions}) => lodashMap(transactions, (t) => `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${t.transactionID}`), + key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, }, }), )(LHNOptionsList); From 9526c1be2cf91f4d37904b07d61e1baca0d79379 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Mon, 18 Dec 2023 13:37:54 +0100 Subject: [PATCH 068/161] fix: app crash --- src/libs/actions/Task.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index 9f2f01e04623..f4a01fa7cf3c 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -871,7 +871,7 @@ function getTaskAssigneeAccountID(taskReport: OnyxTypes.Report): number | null | * Returns Task owner accountID */ function getTaskOwnerAccountID(taskReport: OnyxTypes.Report): number | null { - return taskReport.ownerAccountID ?? null; + return taskReport?.ownerAccountID ?? null; } /** From d4678532df22c35f90852c51dc4289cb15fdfa70 Mon Sep 17 00:00:00 2001 From: Trevor Coleman Date: Mon, 18 Dec 2023 20:47:38 -0500 Subject: [PATCH 069/161] feat(Violations): fix propsTypes --- src/libs/ReportUtils.ts | 6 ++++-- src/libs/Violations/propTypes.ts | 2 +- src/pages/home/ReportScreen.js | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 0eda7f18a5ac..61e8845c95d0 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3477,9 +3477,11 @@ function shouldHideReport(report: OnyxEntry, currentReportId: string): b return parentReport?.reportID !== report?.reportID && !isChildReportHasComment; } +/** + * Checks if any violations for the provided transaction are of type 'violation' + */ function transactionHasViolation(transactionID: string, transactionViolations: TransactionViolations): boolean { - const violations: TransactionViolation[] = transactionViolations[transactionID]; - return violations.some((violation: TransactionViolation) => violation.type === 'violation'); + return transactionViolations[transactionID]?.some((violation: TransactionViolation) => violation.type === 'violation'); } /** diff --git a/src/libs/Violations/propTypes.ts b/src/libs/Violations/propTypes.ts index f0b84cf7d3b1..4b5e84405cba 100644 --- a/src/libs/Violations/propTypes.ts +++ b/src/libs/Violations/propTypes.ts @@ -27,6 +27,6 @@ const transactionViolationPropType = PropTypes.shape({ }), }); -const transactionViolationsPropType = PropTypes.objectOf(transactionViolationPropType); +const transactionViolationsPropType = PropTypes.objectOf(PropTypes.arrayOf(transactionViolationPropType)); export {transactionViolationsPropType, transactionViolationPropType}; diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index 161b8aa8889d..c3154d600672 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -66,7 +66,7 @@ const propTypes = { reportMetadata: reportMetadataPropTypes, /** Array of report actions for this report */ - reportActions: PropTypes.arrayOf(PropTypes.shape(reportActionPropTypes)), + reportActions: PropTypes.objectOf(PropTypes.shape(reportActionPropTypes)), /** Whether the composer is full size */ isComposerFullSize: PropTypes.bool, @@ -103,7 +103,7 @@ const propTypes = { const defaultProps = { isSidebarLoaded: false, - reportActions: [], + reportActions: {}, report: {}, reportMetadata: { isLoadingInitialReportActions: true, From 056c4ba11675ca257e9dc1e4b75dfc649d70d4dc Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 19 Dec 2023 14:51:56 +0100 Subject: [PATCH 070/161] fix: type --- src/libs/actions/Task.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index 4dcabf6ef25c..dc3b6181c2f5 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -32,7 +32,7 @@ Onyx.connect({ }, }); -let allPersonalDetails: Record | null; +let allPersonalDetails: OnyxEntry; Onyx.connect({ key: ONYXKEYS.PERSONAL_DETAILS_LIST, callback: (value) => (allPersonalDetails = value), From 69054e2a19b0f102fa4552b72659b594262a5a09 Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 19 Dec 2023 15:02:11 +0100 Subject: [PATCH 071/161] fix: added few return types --- src/libs/ReportUtils.ts | 2 +- src/libs/actions/Task.ts | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 7480d8163204..29cdc95707af 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -4494,4 +4494,4 @@ export { shouldAutoFocusOnKeyPress, }; -export type {OptionData, OptimisticChatReport}; +export type {OptionData, OptimisticChatReport, DisplayNameWithTooltips}; diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index dc3b6181c2f5..ef4eec9271bb 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -20,6 +20,13 @@ import * as Report from './Report'; type OptimisticReport = Pick; type Assignee = {icons: Icon[]; displayName: string; subtitle: string}; +type ShareDestination = { + icons: Icon[]; + displayName: string; + subtitle: string; + displayNamesWithTooltips: ReportUtils.DisplayNameWithTooltips; + shouldUseFullTitleToDisplay: boolean; +}; let currentUserEmail: string | undefined; let currentUserAccountID: number | undefined; @@ -219,6 +226,7 @@ function createTaskAndNavigate( assigneeChatReportActionID?: string; assigneeChatCreatedReportActionID?: string; }; + const parameters: CreateTaskParameters = { parentReportActionID: optimisticAddCommentReport.reportAction.reportActionID, parentReportID, @@ -689,7 +697,7 @@ function getAssignee(assigneeAccountID: number, personalDetails: Record, personalDetails: Record) { +function getShareDestination(reportID: string, reports: Record, personalDetails: Record): ShareDestination { const report = reports[`report_${reportID}`] ?? {}; const participantAccountIDs = report.participantAccountIDs ?? []; From 6557b808f9a4a26ecf9eb314076fecb5929e2eff Mon Sep 17 00:00:00 2001 From: Jakub Butkiewicz Date: Tue, 19 Dec 2023 15:26:35 +0100 Subject: [PATCH 072/161] fix: types --- src/components/TaskHeaderActionButton.tsx | 2 +- src/libs/actions/Task.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/TaskHeaderActionButton.tsx b/src/components/TaskHeaderActionButton.tsx index b1a32b356ae1..1ce17ded4256 100644 --- a/src/components/TaskHeaderActionButton.tsx +++ b/src/components/TaskHeaderActionButton.tsx @@ -31,7 +31,7 @@ function TaskHeaderActionButton({report, session, policy}: TaskHeaderActionButto