From aefde1271ff117e9eaa564f6dc0835234050d462 Mon Sep 17 00:00:00 2001 From: Pavlo Tsimura Date: Sat, 17 Feb 2024 23:25:08 +0100 Subject: [PATCH 1/8] Build optimistic Edit Task actions --- src/libs/OptionsListUtils.ts | 3 +- src/libs/ReportActionsUtils.ts | 4 --- src/libs/ReportUtils.ts | 57 ++++++++++++++++++++++++++++------ src/libs/actions/Task.ts | 4 +-- 4 files changed, 52 insertions(+), 16 deletions(-) diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index d6f718da2b2c..e46b0d933eb1 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -575,7 +575,8 @@ function getLastMessageTextForReport(report: OnyxEntry, lastActorDetails } else if ( lastActionName === CONST.REPORT.ACTIONS.TYPE.TASKCOMPLETED || lastActionName === CONST.REPORT.ACTIONS.TYPE.TASKREOPENED || - lastActionName === CONST.REPORT.ACTIONS.TYPE.TASKCANCELLED + lastActionName === CONST.REPORT.ACTIONS.TYPE.TASKCANCELLED || + lastActionName === CONST.REPORT.ACTIONS.TYPE.TASKEDITED ) { lastMessageTextFromReport = lastReportAction?.message?.[0].text ?? ''; } else if (ReportActionUtils.isCreatedTaskReportAction(lastReportAction)) { diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index eeebbdf7dbdb..db2ca4fec910 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -379,10 +379,6 @@ function shouldReportActionBeVisible(reportAction: OnyxEntry, key: return false; } - if (reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.TASKEDITED) { - return false; - } - // Filter out any unsupported reportAction types if (!supportedActionTypes.includes(reportAction.actionName)) { return false; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 453de7219315..fcaa7934a938 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -26,6 +26,7 @@ import type { ReportAction, ReportMetadata, Session, + Task, Transaction, TransactionViolation, } from '@src/types/onyx'; @@ -3568,25 +3569,62 @@ function buildOptimisticUnHoldReportAction(created = DateUtils.getDBTime()): Opt }; } -/** - * Returns the necessary reportAction onyx data to indicate that a task report has been edited - */ -function buildOptimisticEditedTaskReportAction(emailEditingTask: string): OptimisticEditedTaskReportAction { +function buildOptimisticEditedTaskFieldReportAction({title, description}: Task): OptimisticEditedTaskReportAction { + // We do not modify title & description in one request, so we need to create a different optimistic action for each field modification + let field = ''; + let value = ''; + if (title !== undefined) { + field = 'task title'; + value = title; + } else if (description !== undefined) { + field = 'description'; + value = description; + } + + let changelog = 'edited this task'; + if (field && value) { + changelog = `updated the ${field} to ${value}`; + } else if (field) { + changelog = `removed the ${field}`; + } + return { reportActionID: NumberUtils.rand64(), actionName: CONST.REPORT.ACTIONS.TYPE.TASKEDITED, pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, actorAccountID: currentUserAccountID, message: [ + { + type: CONST.REPORT.MESSAGE.TYPE.COMMENT, + text: changelog, + html: changelog, + }, + ], + person: [ { type: CONST.REPORT.MESSAGE.TYPE.TEXT, style: 'strong', - text: emailEditingTask, + text: allPersonalDetails?.[currentUserAccountID ?? '']?.displayName ?? currentUserEmail, }, + ], + automatic: false, + avatar: getCurrentUserAvatarOrDefault(), + created: DateUtils.getDBTime(), + shouldShow: false, + }; +} + +function buildOptimisticChangedTaskAssigneeReportAction(assigneeAccountID: number): OptimisticEditedTaskReportAction { + return { + reportActionID: NumberUtils.rand64(), + actionName: CONST.REPORT.ACTIONS.TYPE.TASKEDITED, + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + actorAccountID: currentUserAccountID, + message: [ { - type: CONST.REPORT.MESSAGE.TYPE.TEXT, - style: 'normal', - text: ' edited this task', + type: CONST.REPORT.MESSAGE.TYPE.COMMENT, + text: `assigned to ${getDisplayNameForParticipant(assigneeAccountID)}`, + html: `assigned to `, }, ], person: [ @@ -5077,7 +5115,8 @@ export { buildOptimisticClosedReportAction, buildOptimisticCreatedReportAction, buildOptimisticRenamedRoomReportAction, - buildOptimisticEditedTaskReportAction, + buildOptimisticEditedTaskFieldReportAction, + buildOptimisticChangedTaskAssigneeReportAction, buildOptimisticIOUReport, buildOptimisticApprovedReportAction, buildOptimisticMovedReportAction, diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index 28cecf460a5f..b4f2a4c9f294 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -383,7 +383,7 @@ function reopenTask(taskReport: OnyxEntry) { function editTask(report: OnyxTypes.Report, {title, description}: OnyxTypes.Task) { // Create the EditedReportAction on the task - const editTaskReportAction = ReportUtils.buildOptimisticEditedTaskReportAction(currentUserEmail); + const editTaskReportAction = ReportUtils.buildOptimisticEditedTaskFieldReportAction({title, description}); // 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(); @@ -453,7 +453,7 @@ function editTask(report: OnyxTypes.Report, {title, description}: OnyxTypes.Task function editTaskAssignee(report: OnyxTypes.Report, ownerAccountID: number, assigneeEmail: string, assigneeAccountID = 0, assigneeChatReport: OnyxEntry = null) { // Create the EditedReportAction on the task - const editTaskReportAction = ReportUtils.buildOptimisticEditedTaskReportAction(currentUserEmail); + const editTaskReportAction = ReportUtils.buildOptimisticChangedTaskAssigneeReportAction(assigneeAccountID); const reportName = report.reportName?.trim(); let assigneeChatReportOnyxData; From c4dcda168fcc51c75b65e1e570707e6f7c2f0c25 Mon Sep 17 00:00:00 2001 From: Pavlo Tsimura Date: Tue, 20 Feb 2024 23:13:33 +0100 Subject: [PATCH 2/8] DRY --- src/libs/ReportUtils.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index b943d08875f5..49d0b969a3e1 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -3438,6 +3438,10 @@ function getCurrentUserAvatarOrDefault(): UserUtils.AvatarSource { return allPersonalDetails?.[currentUserAccountID ?? '']?.avatar ?? UserUtils.getDefaultAvatarURL(currentUserAccountID); } +function getCurrentUserDisplayNameOrEmail(): string | undefined { + return allPersonalDetails?.[currentUserAccountID ?? '']?.displayName ?? currentUserEmail; +} + /** * Returns the necessary reportAction onyx data to indicate that the chat has been created optimistically * @param [created] - Action created time @@ -3464,7 +3468,7 @@ function buildOptimisticCreatedReportAction(emailCreatingAction: string, created { type: CONST.REPORT.MESSAGE.TYPE.TEXT, style: 'strong', - text: allPersonalDetails?.[currentUserAccountID ?? '']?.displayName ?? currentUserEmail, + text: getCurrentUserDisplayNameOrEmail(), }, ], automatic: false, @@ -3500,7 +3504,7 @@ function buildOptimisticRenamedRoomReportAction(newName: string, oldName: string { type: CONST.REPORT.MESSAGE.TYPE.TEXT, style: 'strong', - text: allPersonalDetails?.[currentUserAccountID ?? '']?.displayName ?? currentUserEmail, + text: getCurrentUserDisplayNameOrEmail(), }, ], originalMessage: { @@ -3541,7 +3545,7 @@ function buildOptimisticHoldReportAction(comment: string, created = DateUtils.ge { type: CONST.REPORT.MESSAGE.TYPE.TEXT, style: 'strong', - text: allPersonalDetails?.[currentUserAccountID ?? '']?.displayName ?? currentUserEmail, + text: getCurrentUserDisplayNameOrEmail(), }, ], automatic: false, @@ -3572,7 +3576,7 @@ function buildOptimisticUnHoldReportAction(created = DateUtils.getDBTime()): Opt { type: CONST.REPORT.MESSAGE.TYPE.TEXT, style: 'normal', - text: allPersonalDetails?.[currentUserAccountID ?? '']?.displayName ?? currentUserEmail, + text: getCurrentUserDisplayNameOrEmail(), }, ], automatic: false, @@ -3617,7 +3621,7 @@ function buildOptimisticEditedTaskFieldReportAction({title, description}: Task): { type: CONST.REPORT.MESSAGE.TYPE.TEXT, style: 'strong', - text: allPersonalDetails?.[currentUserAccountID ?? '']?.displayName ?? currentUserEmail, + text: getCurrentUserDisplayNameOrEmail(), }, ], automatic: false, @@ -3644,7 +3648,7 @@ function buildOptimisticChangedTaskAssigneeReportAction(assigneeAccountID: numbe { type: CONST.REPORT.MESSAGE.TYPE.TEXT, style: 'strong', - text: allPersonalDetails?.[currentUserAccountID ?? '']?.displayName ?? currentUserEmail, + text: getCurrentUserDisplayNameOrEmail(), }, ], automatic: false, @@ -3687,7 +3691,7 @@ function buildOptimisticClosedReportAction(emailClosingReport: string, policyNam { type: CONST.REPORT.MESSAGE.TYPE.TEXT, style: 'strong', - text: allPersonalDetails?.[currentUserAccountID ?? '']?.displayName ?? currentUserEmail, + text: getCurrentUserDisplayNameOrEmail(), }, ], reportActionID: NumberUtils.rand64(), From adc314e531ba9ae8e7a8542d7607de1371b5c6e5 Mon Sep 17 00:00:00 2001 From: Pavlo Tsimura Date: Wed, 21 Feb 2024 22:18:40 +0100 Subject: [PATCH 3/8] No longer hide the TASKEDITED actions --- tests/unit/ReportActionsUtilsTest.js | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/unit/ReportActionsUtilsTest.js b/tests/unit/ReportActionsUtilsTest.js index 19a89d1c892c..b0308b0222c3 100644 --- a/tests/unit/ReportActionsUtilsTest.js +++ b/tests/unit/ReportActionsUtilsTest.js @@ -187,7 +187,6 @@ describe('ReportActionsUtils', () => { ]; const result = ReportActionsUtils.getSortedReportActionsForDisplay(input); - input.pop(); expect(result).toStrictEqual(input); }); From c35a5103632702c992cc7a462a2a7de8206ced2d Mon Sep 17 00:00:00 2001 From: Pavlo Tsimura Date: Thu, 29 Feb 2024 02:09:33 +0100 Subject: [PATCH 4/8] Reformat task-related actions --- src/components/ReportActionItem/TaskAction.tsx | 10 +++++++--- src/libs/OptionsListUtils.ts | 10 ++-------- src/libs/ReportActionsUtils.ts | 3 ++- src/libs/SidebarUtils.ts | 2 +- src/libs/TaskUtils.ts | 18 ++++++++++++------ src/libs/actions/Task.ts | 10 ++++++++++ .../report/ContextMenu/ContextMenuActions.tsx | 6 ++++-- src/pages/home/report/ReportActionItem.js | 8 ++------ 8 files changed, 40 insertions(+), 27 deletions(-) diff --git a/src/components/ReportActionItem/TaskAction.tsx b/src/components/ReportActionItem/TaskAction.tsx index b10be4e86fe8..7e9262bb4c05 100644 --- a/src/components/ReportActionItem/TaskAction.tsx +++ b/src/components/ReportActionItem/TaskAction.tsx @@ -1,20 +1,24 @@ import React from 'react'; import {View} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; +import RenderHTML from '@components/RenderHTML'; import Text from '@components/Text'; import useThemeStyles from '@hooks/useThemeStyles'; import * as TaskUtils from '@libs/TaskUtils'; +import type {ReportAction} from '@src/types/onyx'; type TaskActionProps = { /** Name of the reportAction action */ - actionName: string; + action: OnyxEntry; }; -function TaskAction({actionName}: TaskActionProps) { +function TaskAction({action}: TaskActionProps) { const styles = useThemeStyles(); + const message = TaskUtils.getTaskReportActionMessage(action); return ( - {TaskUtils.getTaskReportActionMessage(actionName)} + {message.html ? ${message.html}`} /> : {message.text}} ); } diff --git a/src/libs/OptionsListUtils.ts b/src/libs/OptionsListUtils.ts index c2c6b01a676c..4019b6effcf5 100644 --- a/src/libs/OptionsListUtils.ts +++ b/src/libs/OptionsListUtils.ts @@ -524,7 +524,6 @@ function getLastMessageTextForReport(report: OnyxEntry, lastActorDetails // some types of actions are filtered out for lastReportAction, in some cases we need to check the actual last action const lastOriginalReportAction = lastReportActions[report?.reportID ?? ''] ?? null; let lastMessageTextFromReport = ''; - const lastActionName = lastReportAction?.actionName ?? ''; if (ReportUtils.isArchivedRoom(report)) { const archiveReason = @@ -576,13 +575,8 @@ function getLastMessageTextForReport(report: OnyxEntry, lastActorDetails } else if (ReportActionUtils.isModifiedExpenseAction(lastReportAction)) { const properSchemaForModifiedExpenseMessage = ModifiedExpenseMessage.getForReportAction(report?.reportID, lastReportAction); lastMessageTextFromReport = ReportUtils.formatReportLastMessageText(properSchemaForModifiedExpenseMessage, true); - } else if ( - lastActionName === CONST.REPORT.ACTIONS.TYPE.TASKCOMPLETED || - lastActionName === CONST.REPORT.ACTIONS.TYPE.TASKREOPENED || - lastActionName === CONST.REPORT.ACTIONS.TYPE.TASKCANCELLED || - lastActionName === CONST.REPORT.ACTIONS.TYPE.TASKEDITED - ) { - lastMessageTextFromReport = lastReportAction?.message?.[0].text ?? ''; + } else if (ReportActionUtils.isTaskAction(lastReportAction)) { + lastMessageTextFromReport = TaskUtils.getTaskReportActionMessage(lastReportAction).text; } else if (ReportActionUtils.isCreatedTaskReportAction(lastReportAction)) { lastMessageTextFromReport = TaskUtils.getTaskCreatedMessage(lastReportAction); } diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index c1e97a4717bd..818e9d20e900 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -671,7 +671,8 @@ function isTaskAction(reportAction: OnyxEntry): boolean { return ( reportActionName === CONST.REPORT.ACTIONS.TYPE.TASKCOMPLETED || reportActionName === CONST.REPORT.ACTIONS.TYPE.TASKCANCELLED || - reportActionName === CONST.REPORT.ACTIONS.TYPE.TASKREOPENED + reportActionName === CONST.REPORT.ACTIONS.TYPE.TASKREOPENED || + reportActionName === CONST.REPORT.ACTIONS.TYPE.TASKEDITED ); } diff --git a/src/libs/SidebarUtils.ts b/src/libs/SidebarUtils.ts index 35cf52a5ff99..7e7ce352de81 100644 --- a/src/libs/SidebarUtils.ts +++ b/src/libs/SidebarUtils.ts @@ -301,7 +301,7 @@ function getOptionData({ const newName = lastAction?.originalMessage?.newName ?? ''; result.alternateText = Localize.translate(preferredLocale, 'newRoomPage.roomRenamedTo', {newName}); } else if (ReportActionsUtils.isTaskAction(lastAction)) { - result.alternateText = TaskUtils.getTaskReportActionMessage(lastAction.actionName); + result.alternateText = TaskUtils.getTaskReportActionMessage(lastAction).text; } else if ( lastAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.INVITE_TO_ROOM || lastAction?.actionName === CONST.REPORT.ACTIONS.TYPE.ROOMCHANGELOG.REMOVE_FROM_ROOM || diff --git a/src/libs/TaskUtils.ts b/src/libs/TaskUtils.ts index 623d449db885..81a079003d0e 100644 --- a/src/libs/TaskUtils.ts +++ b/src/libs/TaskUtils.ts @@ -3,6 +3,7 @@ import Onyx from 'react-native-onyx'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Report} from '@src/types/onyx'; +import type {Message} from '@src/types/onyx/ReportAction'; import type ReportAction from '@src/types/onyx/ReportAction'; import * as CollectionUtils from './CollectionUtils'; import * as Localize from './Localize'; @@ -22,16 +23,21 @@ Onyx.connect({ /** * Given the Task reportAction name, return the appropriate message to be displayed and copied to clipboard. */ -function getTaskReportActionMessage(actionName: string): string { - switch (actionName) { +function getTaskReportActionMessage(action: OnyxEntry): Pick { + switch (action?.actionName) { case CONST.REPORT.ACTIONS.TYPE.TASKCOMPLETED: - return Localize.translateLocal('task.messages.completed'); + return {text: Localize.translateLocal('task.messages.completed')}; case CONST.REPORT.ACTIONS.TYPE.TASKCANCELLED: - return Localize.translateLocal('task.messages.canceled'); + return {text: Localize.translateLocal('task.messages.canceled')}; case CONST.REPORT.ACTIONS.TYPE.TASKREOPENED: - return Localize.translateLocal('task.messages.reopened'); + return {text: Localize.translateLocal('task.messages.reopened')}; + case CONST.REPORT.ACTIONS.TYPE.TASKEDITED: + return { + text: action?.message?.[0].text ?? '', + html: action?.message?.[0].html, + }; default: - return Localize.translateLocal('task.task'); + return {text: Localize.translateLocal('task.task')}; } } diff --git a/src/libs/actions/Task.ts b/src/libs/actions/Task.ts index 48ba3196964c..d8ca9fcfe51c 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -413,6 +413,11 @@ function editTask(report: OnyxTypes.Report, {title, description}: OnyxTypes.Task ]; const successData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`, + value: {[editTaskReportAction.reportActionID]: {pendingAction: null}}, + }, { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, @@ -483,6 +488,11 @@ function editTaskAssignee(report: OnyxTypes.Report, ownerAccountID: number, assi ]; const successData: OnyxUpdate[] = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${report.reportID}`, + value: {[editTaskReportAction.reportActionID]: {pendingAction: null}}, + }, { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.tsx b/src/pages/home/report/ContextMenu/ContextMenuActions.tsx index 51e6b25f1314..d94d8d1a1fff 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.tsx +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.tsx @@ -338,9 +338,8 @@ const ContextMenuActions: ContextMenuAction[] = [ // `ContextMenuItem` with `successText` and `successIcon` which will fall back to // the `text` and `icon` onPress: (closePopover, {reportAction, selection, reportID}) => { - const isTaskAction = ReportActionsUtils.isTaskAction(reportAction); const isReportPreviewAction = ReportActionsUtils.isReportPreviewAction(reportAction); - const messageHtml = isTaskAction ? TaskUtils.getTaskReportActionMessage(reportAction?.actionName) : getActionHtml(reportAction); + const messageHtml = getActionHtml(reportAction); const messageText = getActionText(reportAction); const isAttachment = ReportActionsUtils.isReportActionAttachment(reportAction); @@ -350,6 +349,9 @@ const ContextMenuActions: ContextMenuAction[] = [ const iouReport = ReportUtils.getReport(ReportActionsUtils.getIOUReportIDFromReportActionPreview(reportAction)); const displayMessage = ReportUtils.getReportPreviewMessage(iouReport, reportAction); Clipboard.setString(displayMessage); + } else if (ReportActionsUtils.isTaskAction(reportAction)) { + const displayMessage = TaskUtils.getTaskReportActionMessage(reportAction).text; + Clipboard.setString(displayMessage); } else if (ReportActionsUtils.isModifiedExpenseAction(reportAction)) { const modifyExpenseMessage = ModifiedExpenseMessage.getForReportAction(reportID, reportAction); Clipboard.setString(modifyExpenseMessage); diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index c0839cd76da9..642e5b42cbfc 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -393,12 +393,8 @@ function ReportActionItem(props) { transactionViolations={props.transactionViolations} /> ); - } else if ( - props.action.actionName === CONST.REPORT.ACTIONS.TYPE.TASKCOMPLETED || - props.action.actionName === CONST.REPORT.ACTIONS.TYPE.TASKCANCELLED || - props.action.actionName === CONST.REPORT.ACTIONS.TYPE.TASKREOPENED - ) { - children = ; + } else if (ReportActionsUtils.isTaskAction(props.action)) { + children = ; } else if (ReportActionsUtils.isCreatedTaskReportAction(props.action)) { children = ( From d904a6ea0d4cc91ee2e4483dacb5c65dcc8924b5 Mon Sep 17 00:00:00 2001 From: Pavlo Tsimura Date: Mon, 4 Mar 2024 14:20:30 +0100 Subject: [PATCH 5/8] Reuse functions to get current user data --- src/libs/ReportUtils.ts | 42 ++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index e3d7214dd03d..565f7a936c10 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -516,6 +516,14 @@ Onyx.connect({ }, }); +function getCurrentUserAvatarOrDefault(): UserUtils.AvatarSource { + return currentUserPersonalDetails?.avatar ?? UserUtils.getDefaultAvatarURL(currentUserAccountID); +} + +function getCurrentUserDisplayNameOrEmail(): string | undefined { + return currentUserPersonalDetails?.displayName ?? currentUserEmail; +} + function getChatType(report: OnyxEntry | Participant | EmptyObject): ValueOf | undefined { return report?.chatType; } @@ -1798,7 +1806,7 @@ function buildOptimisticCancelPaymentReportAction(expenseReportID: string, amoun person: [ { style: 'strong', - text: currentUserPersonalDetails?.displayName ?? currentUserEmail, + text: getCurrentUserDisplayNameOrEmail(), type: 'TEXT', }, ], @@ -3133,14 +3141,14 @@ function buildOptimisticIOUReportAction( actionName: CONST.REPORT.ACTIONS.TYPE.IOU, actorAccountID: currentUserAccountID, automatic: false, - avatar: currentUserPersonalDetails?.avatar ?? UserUtils.getDefaultAvatarURL(currentUserAccountID), + avatar: getCurrentUserAvatarOrDefault(), isAttachment: false, originalMessage, message: getIOUReportActionMessage(iouReportID, type, amount, comment, currency, paymentType, isSettlingUp), person: [ { style: 'strong', - text: currentUserPersonalDetails?.displayName ?? currentUserEmail, + text: getCurrentUserDisplayNameOrEmail(), type: 'TEXT', }, ], @@ -3166,14 +3174,14 @@ function buildOptimisticApprovedReportAction(amount: number, currency: string, e actionName: CONST.REPORT.ACTIONS.TYPE.APPROVED, actorAccountID: currentUserAccountID, automatic: false, - avatar: currentUserPersonalDetails?.avatar ?? UserUtils.getDefaultAvatarURL(currentUserAccountID), + avatar: getCurrentUserAvatarOrDefault(), isAttachment: false, originalMessage, message: getIOUReportActionMessage(expenseReportID, CONST.REPORT.ACTIONS.TYPE.APPROVED, Math.abs(amount), '', currency), person: [ { style: 'strong', - text: currentUserPersonalDetails?.displayName ?? currentUserEmail, + text: getCurrentUserDisplayNameOrEmail(), type: 'TEXT', }, ], @@ -3208,14 +3216,14 @@ function buildOptimisticMovedReportAction(fromPolicyID: string, toPolicyID: stri actionName: CONST.REPORT.ACTIONS.TYPE.MOVED, actorAccountID: currentUserAccountID, automatic: false, - avatar: currentUserPersonalDetails?.avatar ?? UserUtils.getDefaultAvatarURL(currentUserAccountID), + avatar: getCurrentUserAvatarOrDefault(), isAttachment: false, originalMessage, message: movedActionMessage, person: [ { style: 'strong', - text: currentUserPersonalDetails?.displayName ?? currentUserEmail, + text: getCurrentUserDisplayNameOrEmail(), type: 'TEXT', }, ], @@ -3241,14 +3249,14 @@ function buildOptimisticSubmittedReportAction(amount: number, currency: string, actionName: CONST.REPORT.ACTIONS.TYPE.SUBMITTED, actorAccountID: currentUserAccountID, automatic: false, - avatar: currentUserPersonalDetails?.avatar ?? UserUtils.getDefaultAvatar(currentUserAccountID), + avatar: getCurrentUserAvatarOrDefault(), isAttachment: false, originalMessage, message: getIOUReportActionMessage(expenseReportID, CONST.REPORT.ACTIONS.TYPE.SUBMITTED, Math.abs(amount), '', currency), person: [ { style: 'strong', - text: currentUserPersonalDetails?.displayName ?? currentUserEmail, + text: getCurrentUserDisplayNameOrEmail(), type: 'TEXT', }, ], @@ -3314,7 +3322,7 @@ function buildOptimisticModifiedExpenseReportAction( actionName: CONST.REPORT.ACTIONS.TYPE.MODIFIEDEXPENSE, actorAccountID: currentUserAccountID, automatic: false, - avatar: currentUserPersonalDetails?.avatar ?? UserUtils.getDefaultAvatarURL(currentUserAccountID), + avatar: getCurrentUserAvatarOrDefault(), created: DateUtils.getDBTime(), isAttachment: false, message: [ @@ -3397,7 +3405,7 @@ function buildOptimisticTaskReportAction(taskReportID: string, actionName: Origi actionName, actorAccountID: currentUserAccountID, automatic: false, - avatar: currentUserPersonalDetails?.avatar ?? UserUtils.getDefaultAvatarURL(currentUserAccountID), + avatar: getCurrentUserAvatarOrDefault(), isAttachment: false, originalMessage, message: [ @@ -3473,14 +3481,6 @@ function buildOptimisticChatReport( }; } -function getCurrentUserAvatarOrDefault(): UserUtils.AvatarSource { - return allPersonalDetails?.[currentUserAccountID ?? '']?.avatar ?? UserUtils.getDefaultAvatarURL(currentUserAccountID); -} - -function getCurrentUserDisplayNameOrEmail(): string | undefined { - return allPersonalDetails?.[currentUserAccountID ?? '']?.displayName ?? currentUserEmail; -} - /** * Returns the necessary reportAction onyx data to indicate that the chat has been created optimistically * @param [created] - Action created time @@ -3588,7 +3588,7 @@ function buildOptimisticHoldReportAction(comment: string, created = DateUtils.ge }, ], automatic: false, - avatar: allPersonalDetails?.[currentUserAccountID ?? '']?.avatar ?? UserUtils.getDefaultAvatarURL(currentUserAccountID), + avatar: getCurrentUserAvatarOrDefault(), created, shouldShow: true, }; @@ -3619,7 +3619,7 @@ function buildOptimisticUnHoldReportAction(created = DateUtils.getDBTime()): Opt }, ], automatic: false, - avatar: allPersonalDetails?.[currentUserAccountID ?? '']?.avatar ?? UserUtils.getDefaultAvatarURL(currentUserAccountID), + avatar: getCurrentUserAvatarOrDefault(), created, shouldShow: true, }; From a86a2f7d2c968526ac2215f9505a4d93f971af2a Mon Sep 17 00:00:00 2001 From: Pavlo Tsimura Date: Wed, 6 Mar 2024 20:04:31 +0100 Subject: [PATCH 6/8] Localize taskEdited messages --- .../ReportActionItem/TaskPreview.tsx | 2 +- src/languages/en.ts | 6 ++ src/languages/es.ts | 6 ++ src/languages/types.ts | 9 ++ src/libs/ReportUtils.ts | 96 +++++++++---------- src/libs/TaskUtils.ts | 55 ++++++++++- src/libs/actions/Task.ts | 37 ++----- src/types/onyx/OriginalMessage.ts | 16 +++- 8 files changed, 143 insertions(+), 84 deletions(-) diff --git a/src/components/ReportActionItem/TaskPreview.tsx b/src/components/ReportActionItem/TaskPreview.tsx index f0d9ad88868c..78221ac89849 100644 --- a/src/components/ReportActionItem/TaskPreview.tsx +++ b/src/components/ReportActionItem/TaskPreview.tsx @@ -72,7 +72,7 @@ function TaskPreview({taskReport, taskReportID, action, contextMenuAnchor, chatR ? taskReport?.stateNum === CONST.REPORT.STATE_NUM.APPROVED && taskReport.statusNum === CONST.REPORT.STATUS_NUM.APPROVED : action?.childStateNum === CONST.REPORT.STATE_NUM.APPROVED && action?.childStatusNum === CONST.REPORT.STATUS_NUM.APPROVED; const taskTitle = Str.htmlEncode(TaskUtils.getTaskTitle(taskReportID, action?.childReportName ?? '')); - const taskAssigneeAccountID = Task.getTaskAssigneeAccountID(taskReport) ?? action?.childManagerAccountID ?? ''; + const taskAssigneeAccountID = ReportUtils.getTaskAssigneeAccountID(taskReport) ?? action?.childManagerAccountID ?? ''; const htmlForTaskPreview = taskAssigneeAccountID !== 0 ? ` ${taskTitle}` : `${taskTitle}`; const isDeletedParentAction = ReportUtils.isCanceledTaskReport(taskReport, action); diff --git a/src/languages/en.ts b/src/languages/en.ts index 0a52cca62ef5..c7c458a6f955 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -49,6 +49,7 @@ import type { PayerPaidAmountParams, PayerPaidParams, PayerSettledParams, + RemovedTaskFieldParams, RemovedTheRequestParams, RenamedRoomActionParams, ReportArchiveReasonsClosedParams, @@ -77,6 +78,8 @@ import type { TransferParams, TranslationBase, UntilTimeParams, + UpdatedTaskAssigneeParams, + UpdatedTaskFieldParams, UpdatedTheDistanceParams, UpdatedTheRequestParams, UsePlusButtonParams, @@ -2019,6 +2022,9 @@ export default { canceled: 'deleted task', reopened: 'marked as incomplete', error: 'You do not have the permission to do the requested action.', + updatedField: ({fieldName, newValueToDisplay}: UpdatedTaskFieldParams) => `updated the ${fieldName} to "${newValueToDisplay}"`, + removedField: ({fieldName}: RemovedTaskFieldParams) => `removed the ${fieldName}`, + updatedAssignee: ({assignee}: UpdatedTaskAssigneeParams) => `assigned to ${assignee}`, }, markAsComplete: 'Mark as complete', markAsIncomplete: 'Mark as incomplete', diff --git a/src/languages/es.ts b/src/languages/es.ts index 013255c1e11e..05f15aad79d6 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -48,6 +48,7 @@ import type { PayerPaidAmountParams, PayerPaidParams, PayerSettledParams, + RemovedTaskFieldParams, RemovedTheRequestParams, RenamedRoomActionParams, ReportArchiveReasonsClosedParams, @@ -75,6 +76,8 @@ import type { ToValidateLoginParams, TransferParams, UntilTimeParams, + UpdatedTaskAssigneeParams, + UpdatedTaskFieldParams, UpdatedTheDistanceParams, UpdatedTheRequestParams, UsePlusButtonParams, @@ -2046,6 +2049,9 @@ export default { canceled: 'tarea eliminada', reopened: 'marcada como incompleta', error: 'No tiene permiso para realizar la acción solicitada.', + updatedField: ({fieldName, newValueToDisplay}: UpdatedTaskFieldParams) => `cambió ${fieldName === 'título' ? 'el' : 'la'} ${fieldName} a "${newValueToDisplay}"`, + removedField: ({fieldName}: RemovedTaskFieldParams) => `eliminó la ${fieldName}`, + updatedAssignee: ({assignee}: UpdatedTaskAssigneeParams) => `asignó a ${assignee}`, }, markAsComplete: 'Marcar como completada', markAsIncomplete: 'Marcar como incompleta', diff --git a/src/languages/types.ts b/src/languages/types.ts index 2bb05b614483..f65e67d3fc8e 100644 --- a/src/languages/types.ts +++ b/src/languages/types.ts @@ -214,6 +214,12 @@ type TagSelectionParams = {tagName: string}; type WalletProgramParams = {walletProgram: string}; +type UpdatedTaskFieldParams = {fieldName: string; newValueToDisplay: string}; + +type RemovedTaskFieldParams = {fieldName: string}; + +type UpdatedTaskAssigneeParams = {assignee: string}; + type ViolationsAutoReportedRejectedExpenseParams = {rejectedBy: string; rejectReason: string}; type ViolationsCashExpenseWithNoReceiptParams = {formattedLimit?: string}; @@ -399,4 +405,7 @@ export type { ZipCodeExampleFormatParams, LogSizeParams, HeldRequestParams, + UpdatedTaskFieldParams, + RemovedTaskFieldParams, + UpdatedTaskAssigneeParams, }; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index badd688e80a4..9518b352008d 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -41,6 +41,7 @@ import type { OriginalMessageCreated, OriginalMessageReimbursementDequeued, OriginalMessageRenamed, + OriginalMessageTaskEdited, PaymentMethodType, ReimbursementDeQueuedMessage, } from '@src/types/onyx/OriginalMessage'; @@ -209,7 +210,7 @@ type OptimisticCancelPaymentReportAction = Pick< type OptimisticEditedTaskReportAction = Pick< ReportAction, - 'reportActionID' | 'actionName' | 'pendingAction' | 'actorAccountID' | 'automatic' | 'avatar' | 'created' | 'shouldShow' | 'message' | 'person' + 'reportActionID' | 'actionName' | 'pendingAction' | 'actorAccountID' | 'automatic' | 'avatar' | 'created' | 'shouldShow' | 'message' | 'originalMessage' | 'person' >; type OptimisticClosedReportAction = Pick< @@ -2507,6 +2508,41 @@ function getModifiedExpenseOriginalMessage(oldTransaction: OnyxEntry): number | undefined { + if (!report) { + return; + } + + if (report.managerID) { + return report.managerID; + } + + const parentReportAction = ReportActionsUtils.getReportAction(report?.parentReportID ?? '', report?.parentReportActionID ?? ''); + return parentReportAction?.childManagerAccountID; +} + +function getTaskEditedOriginalMessage(oldTask: OnyxEntry, taskChanges: Partial): OriginalMessageTaskEdited['originalMessage'] { + const originalMessage: OriginalMessageTaskEdited['originalMessage'] = {}; + + if ('title' in taskChanges) { + originalMessage.oldTitle = oldTask?.reportName; + originalMessage.title = taskChanges?.title; + } + if ('description' in taskChanges) { + originalMessage.oldDescription = oldTask?.description; + originalMessage.description = taskChanges?.description; + } + if ('assigneeAccountID' in taskChanges) { + originalMessage.oldAssigneeAccountID = getTaskAssigneeAccountID(oldTask); + originalMessage.assigneeAccountID = taskChanges?.assigneeAccountID; + } + + return originalMessage; +} + /** * Check if original message is an object and can be used as a ChangeLog type * @param originalMessage @@ -3651,25 +3687,8 @@ function buildOptimisticUnHoldReportAction(created = DateUtils.getDBTime()): Opt }; } -function buildOptimisticEditedTaskFieldReportAction({title, description}: Task): OptimisticEditedTaskReportAction { - // We do not modify title & description in one request, so we need to create a different optimistic action for each field modification - let field = ''; - let value = ''; - if (title !== undefined) { - field = 'task title'; - value = title; - } else if (description !== undefined) { - field = 'description'; - value = description; - } - - let changelog = 'edited this task'; - if (field && value) { - changelog = `updated the ${field} to ${value}`; - } else if (field) { - changelog = `removed the ${field}`; - } - +function buildOptimisticEditedTaskReportAction(taskReport: Report, changes: Partial): OptimisticEditedTaskReportAction { + const originalMessage = getTaskEditedOriginalMessage(taskReport, changes); return { reportActionID: NumberUtils.rand64(), actionName: CONST.REPORT.ACTIONS.TYPE.TASKEDITED, @@ -3677,38 +3696,13 @@ function buildOptimisticEditedTaskFieldReportAction({title, description}: Task): actorAccountID: currentUserAccountID, message: [ { - type: CONST.REPORT.MESSAGE.TYPE.COMMENT, - text: changelog, - html: changelog, - }, - ], - person: [ - { - type: CONST.REPORT.MESSAGE.TYPE.TEXT, + // Currently, we are composing the message from the originalMessage and the message is not used in the App + text: 'You', style: 'strong', - text: getCurrentUserDisplayNameOrEmail(), - }, - ], - automatic: false, - avatar: getCurrentUserAvatarOrDefault(), - created: DateUtils.getDBTime(), - shouldShow: false, - }; -} - -function buildOptimisticChangedTaskAssigneeReportAction(assigneeAccountID: number): OptimisticEditedTaskReportAction { - return { - reportActionID: NumberUtils.rand64(), - actionName: CONST.REPORT.ACTIONS.TYPE.TASKEDITED, - pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, - actorAccountID: currentUserAccountID, - message: [ - { - type: CONST.REPORT.MESSAGE.TYPE.COMMENT, - text: `assigned to ${getDisplayNameForParticipant(assigneeAccountID)}`, - html: `assigned to `, + type: CONST.REPORT.MESSAGE.TYPE.TEXT, }, ], + originalMessage, person: [ { type: CONST.REPORT.MESSAGE.TYPE.TEXT, @@ -5201,8 +5195,7 @@ export { buildOptimisticClosedReportAction, buildOptimisticCreatedReportAction, buildOptimisticRenamedRoomReportAction, - buildOptimisticEditedTaskFieldReportAction, - buildOptimisticChangedTaskAssigneeReportAction, + buildOptimisticEditedTaskReportAction, buildOptimisticIOUReport, buildOptimisticApprovedReportAction, buildOptimisticMovedReportAction, @@ -5279,6 +5272,7 @@ export { shouldDisableRename, hasSingleParticipant, getReportRecipientAccountIDs, + getTaskAssigneeAccountID, isOneOnOneChat, isPayer, goBackToDetailsPage, diff --git a/src/libs/TaskUtils.ts b/src/libs/TaskUtils.ts index 81a079003d0e..d992b63bbb7f 100644 --- a/src/libs/TaskUtils.ts +++ b/src/libs/TaskUtils.ts @@ -3,10 +3,12 @@ import Onyx from 'react-native-onyx'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Report} from '@src/types/onyx'; +import type {OriginalMessageTaskEdited} from '@src/types/onyx/OriginalMessage'; import type {Message} from '@src/types/onyx/ReportAction'; import type ReportAction from '@src/types/onyx/ReportAction'; import * as CollectionUtils from './CollectionUtils'; import * as Localize from './Localize'; +import * as ReportUtils from './ReportUtils'; const allReports: Record = {}; Onyx.connect({ @@ -20,6 +22,54 @@ Onyx.connect({ }, }); +function buildMessageFragmentForValue(newValue: string | undefined, valueName: string): string { + const fieldName = valueName.toLowerCase(); + + if (!newValue) { + return Localize.translateLocal('task.messages.removedField', {fieldName}); + } + + return Localize.translateLocal('task.messages.updatedField', {fieldName, newValueToDisplay: newValue}); +} + +function getTaskEditedMessage(reportAction: OnyxEntry) { + if (reportAction?.actionName !== CONST.REPORT.ACTIONS.TYPE.TASKEDITED) { + return {text: ''}; + } + + const originalMessage = reportAction?.originalMessage as OriginalMessageTaskEdited['originalMessage'] | undefined; + if (!originalMessage) { + return { + text: reportAction?.message?.[0].text ?? '', + html: reportAction?.message?.[0].html, + }; + } + + const hasModifiedTitle = 'title' in originalMessage; + if (hasModifiedTitle) { + return { + text: buildMessageFragmentForValue(originalMessage.title, Localize.translateLocal('task.title')), + }; + } + + const hasModifiedDescription = 'description' in originalMessage; + if (hasModifiedDescription) { + return { + text: buildMessageFragmentForValue(originalMessage.description, Localize.translateLocal('task.description')), + }; + } + + const hasModifiedAssignee = 'assigneeAccountID' in originalMessage; + if (hasModifiedAssignee) { + return { + text: Localize.translateLocal('task.messages.updatedAssignee', {assignee: ReportUtils.getDisplayNameForParticipant(originalMessage.assigneeAccountID)}), + html: Localize.translateLocal('task.messages.updatedAssignee', {assignee: ``}), + }; + } + + return {text: ''}; +} + /** * Given the Task reportAction name, return the appropriate message to be displayed and copied to clipboard. */ @@ -32,10 +82,7 @@ function getTaskReportActionMessage(action: OnyxEntry): Pick) { API.write(WRITE_COMMANDS.REOPEN_TASK, parameters, {optimisticData, successData, failureData}); } -function editTask(report: OnyxTypes.Report, {title, description}: OnyxTypes.Task) { +function editTask(report: OnyxTypes.Report, changes: Partial) { // Create the EditedReportAction on the task - const editTaskReportAction = ReportUtils.buildOptimisticEditedTaskFieldReportAction({title, description}); + const editTaskReportAction = ReportUtils.buildOptimisticEditedTaskReportAction(report, changes); // 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 = (changes.title ?? report?.reportName)?.trim(); // Description can be unset, so we default to an empty string if so - const reportDescription = (description ?? report.description ?? '').trim(); + const reportDescription = (changes.description ?? report.description ?? '').trim(); const optimisticData: OnyxUpdate[] = [ { @@ -420,8 +420,8 @@ function editTask(report: OnyxTypes.Report, {title, description}: OnyxTypes.Task reportName, description: reportDescription, pendingFields: { - ...(title && {reportName: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), - ...(description && {description: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), + ...(changes.title && {reportName: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), + ...(changes.description && {description: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), }, errorFields: null, }, @@ -439,8 +439,8 @@ function editTask(report: OnyxTypes.Report, {title, description}: OnyxTypes.Task key: `${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, value: { pendingFields: { - ...(title && {reportName: null}), - ...(description && {description: null}), + ...(changes.title && {reportName: null}), + ...(changes.description && {description: null}), }, }, }, @@ -474,7 +474,7 @@ function editTask(report: OnyxTypes.Report, {title, description}: OnyxTypes.Task function editTaskAssignee(report: OnyxTypes.Report, ownerAccountID: number, assigneeEmail: string, assigneeAccountID = 0, assigneeChatReport: OnyxEntry = null) { // Create the EditedReportAction on the task - const editTaskReportAction = ReportUtils.buildOptimisticChangedTaskAssigneeReportAction(assigneeAccountID); + const editTaskReportAction = ReportUtils.buildOptimisticEditedTaskReportAction(report, {assigneeAccountID}); const reportName = report.reportName?.trim(); let assigneeChatReportOnyxData; @@ -889,22 +889,6 @@ function dismissModalAndClearOutTaskInfo() { clearOutTaskInfo(); } -/** - * Returns Task assignee accountID - */ -function getTaskAssigneeAccountID(taskReport: OnyxEntry): number | undefined { - if (!taskReport) { - return; - } - - if (taskReport.managerID) { - return taskReport.managerID; - } - - const reportAction = getParentReportAction(taskReport); - return reportAction.childManagerAccountID; -} - /** * Returns Task owner accountID */ @@ -925,7 +909,7 @@ function canModifyTask(taskReport: OnyxEntry, sessionAccountID return false; } - if (sessionAccountID === getTaskOwnerAccountID(taskReport) || sessionAccountID === getTaskAssigneeAccountID(taskReport)) { + if (sessionAccountID === getTaskOwnerAccountID(taskReport) || sessionAccountID === ReportUtils.getTaskAssigneeAccountID(taskReport)) { return true; } @@ -969,7 +953,6 @@ export { getShareDestination, deleteTask, dismissModalAndClearOutTaskInfo, - getTaskAssigneeAccountID, clearTaskErrors, canModifyTask, }; diff --git a/src/types/onyx/OriginalMessage.ts b/src/types/onyx/OriginalMessage.ts index 06c2d2e6abce..7d682c79c525 100644 --- a/src/types/onyx/OriginalMessage.ts +++ b/src/types/onyx/OriginalMessage.ts @@ -278,6 +278,18 @@ type OriginalMessageMoved = { }; }; +type OriginalMessageTaskEdited = { + actionName: typeof CONST.REPORT.ACTIONS.TYPE.TASKEDITED; + originalMessage: { + oldTitle?: string; + title?: string; + oldDescription?: string; + description?: string; + oldAssigneeAccountID?: number; + assigneeAccountID?: number; + }; +}; + type OriginalMessage = | OriginalMessageApproved | OriginalMessageIOU @@ -298,7 +310,8 @@ type OriginalMessage = | OriginalMessageReimbursementQueued | OriginalMessageReimbursementDequeued | OriginalMessageMoved - | OriginalMessageMarkedReimbursed; + | OriginalMessageMarkedReimbursed + | OriginalMessageTaskEdited; export default OriginalMessage; export type { @@ -319,6 +332,7 @@ export type { OriginalMessageChronosOOOList, OriginalMessageSource, OriginalMessageReimbursementDequeued, + OriginalMessageTaskEdited, DecisionName, PaymentMethodType, }; From f3e618e5f3bc168453a2b33687c62e5be590615a Mon Sep 17 00:00:00 2001 From: Pavlo Tsimura Date: Wed, 6 Mar 2024 20:34:39 +0100 Subject: [PATCH 7/8] Revert "Localize taskEdited messages" This reverts commit a86a2f7d2c968526ac2215f9505a4d93f971af2a. --- .../ReportActionItem/TaskPreview.tsx | 2 +- src/languages/en.ts | 6 -- src/languages/es.ts | 6 -- src/languages/types.ts | 9 -- src/libs/ReportUtils.ts | 96 ++++++++++--------- src/libs/TaskUtils.ts | 55 +---------- src/libs/actions/Task.ts | 37 +++++-- src/types/onyx/OriginalMessage.ts | 16 +--- 8 files changed, 84 insertions(+), 143 deletions(-) diff --git a/src/components/ReportActionItem/TaskPreview.tsx b/src/components/ReportActionItem/TaskPreview.tsx index 78221ac89849..f0d9ad88868c 100644 --- a/src/components/ReportActionItem/TaskPreview.tsx +++ b/src/components/ReportActionItem/TaskPreview.tsx @@ -72,7 +72,7 @@ function TaskPreview({taskReport, taskReportID, action, contextMenuAnchor, chatR ? taskReport?.stateNum === CONST.REPORT.STATE_NUM.APPROVED && taskReport.statusNum === CONST.REPORT.STATUS_NUM.APPROVED : action?.childStateNum === CONST.REPORT.STATE_NUM.APPROVED && action?.childStatusNum === CONST.REPORT.STATUS_NUM.APPROVED; const taskTitle = Str.htmlEncode(TaskUtils.getTaskTitle(taskReportID, action?.childReportName ?? '')); - const taskAssigneeAccountID = ReportUtils.getTaskAssigneeAccountID(taskReport) ?? action?.childManagerAccountID ?? ''; + const taskAssigneeAccountID = Task.getTaskAssigneeAccountID(taskReport) ?? action?.childManagerAccountID ?? ''; const htmlForTaskPreview = taskAssigneeAccountID !== 0 ? ` ${taskTitle}` : `${taskTitle}`; const isDeletedParentAction = ReportUtils.isCanceledTaskReport(taskReport, action); diff --git a/src/languages/en.ts b/src/languages/en.ts index c7c458a6f955..0a52cca62ef5 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -49,7 +49,6 @@ import type { PayerPaidAmountParams, PayerPaidParams, PayerSettledParams, - RemovedTaskFieldParams, RemovedTheRequestParams, RenamedRoomActionParams, ReportArchiveReasonsClosedParams, @@ -78,8 +77,6 @@ import type { TransferParams, TranslationBase, UntilTimeParams, - UpdatedTaskAssigneeParams, - UpdatedTaskFieldParams, UpdatedTheDistanceParams, UpdatedTheRequestParams, UsePlusButtonParams, @@ -2022,9 +2019,6 @@ export default { canceled: 'deleted task', reopened: 'marked as incomplete', error: 'You do not have the permission to do the requested action.', - updatedField: ({fieldName, newValueToDisplay}: UpdatedTaskFieldParams) => `updated the ${fieldName} to "${newValueToDisplay}"`, - removedField: ({fieldName}: RemovedTaskFieldParams) => `removed the ${fieldName}`, - updatedAssignee: ({assignee}: UpdatedTaskAssigneeParams) => `assigned to ${assignee}`, }, markAsComplete: 'Mark as complete', markAsIncomplete: 'Mark as incomplete', diff --git a/src/languages/es.ts b/src/languages/es.ts index 05f15aad79d6..013255c1e11e 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -48,7 +48,6 @@ import type { PayerPaidAmountParams, PayerPaidParams, PayerSettledParams, - RemovedTaskFieldParams, RemovedTheRequestParams, RenamedRoomActionParams, ReportArchiveReasonsClosedParams, @@ -76,8 +75,6 @@ import type { ToValidateLoginParams, TransferParams, UntilTimeParams, - UpdatedTaskAssigneeParams, - UpdatedTaskFieldParams, UpdatedTheDistanceParams, UpdatedTheRequestParams, UsePlusButtonParams, @@ -2049,9 +2046,6 @@ export default { canceled: 'tarea eliminada', reopened: 'marcada como incompleta', error: 'No tiene permiso para realizar la acción solicitada.', - updatedField: ({fieldName, newValueToDisplay}: UpdatedTaskFieldParams) => `cambió ${fieldName === 'título' ? 'el' : 'la'} ${fieldName} a "${newValueToDisplay}"`, - removedField: ({fieldName}: RemovedTaskFieldParams) => `eliminó la ${fieldName}`, - updatedAssignee: ({assignee}: UpdatedTaskAssigneeParams) => `asignó a ${assignee}`, }, markAsComplete: 'Marcar como completada', markAsIncomplete: 'Marcar como incompleta', diff --git a/src/languages/types.ts b/src/languages/types.ts index f65e67d3fc8e..2bb05b614483 100644 --- a/src/languages/types.ts +++ b/src/languages/types.ts @@ -214,12 +214,6 @@ type TagSelectionParams = {tagName: string}; type WalletProgramParams = {walletProgram: string}; -type UpdatedTaskFieldParams = {fieldName: string; newValueToDisplay: string}; - -type RemovedTaskFieldParams = {fieldName: string}; - -type UpdatedTaskAssigneeParams = {assignee: string}; - type ViolationsAutoReportedRejectedExpenseParams = {rejectedBy: string; rejectReason: string}; type ViolationsCashExpenseWithNoReceiptParams = {formattedLimit?: string}; @@ -405,7 +399,4 @@ export type { ZipCodeExampleFormatParams, LogSizeParams, HeldRequestParams, - UpdatedTaskFieldParams, - RemovedTaskFieldParams, - UpdatedTaskAssigneeParams, }; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 9518b352008d..badd688e80a4 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -41,7 +41,6 @@ import type { OriginalMessageCreated, OriginalMessageReimbursementDequeued, OriginalMessageRenamed, - OriginalMessageTaskEdited, PaymentMethodType, ReimbursementDeQueuedMessage, } from '@src/types/onyx/OriginalMessage'; @@ -210,7 +209,7 @@ type OptimisticCancelPaymentReportAction = Pick< type OptimisticEditedTaskReportAction = Pick< ReportAction, - 'reportActionID' | 'actionName' | 'pendingAction' | 'actorAccountID' | 'automatic' | 'avatar' | 'created' | 'shouldShow' | 'message' | 'originalMessage' | 'person' + 'reportActionID' | 'actionName' | 'pendingAction' | 'actorAccountID' | 'automatic' | 'avatar' | 'created' | 'shouldShow' | 'message' | 'person' >; type OptimisticClosedReportAction = Pick< @@ -2508,41 +2507,6 @@ function getModifiedExpenseOriginalMessage(oldTransaction: OnyxEntry): number | undefined { - if (!report) { - return; - } - - if (report.managerID) { - return report.managerID; - } - - const parentReportAction = ReportActionsUtils.getReportAction(report?.parentReportID ?? '', report?.parentReportActionID ?? ''); - return parentReportAction?.childManagerAccountID; -} - -function getTaskEditedOriginalMessage(oldTask: OnyxEntry, taskChanges: Partial): OriginalMessageTaskEdited['originalMessage'] { - const originalMessage: OriginalMessageTaskEdited['originalMessage'] = {}; - - if ('title' in taskChanges) { - originalMessage.oldTitle = oldTask?.reportName; - originalMessage.title = taskChanges?.title; - } - if ('description' in taskChanges) { - originalMessage.oldDescription = oldTask?.description; - originalMessage.description = taskChanges?.description; - } - if ('assigneeAccountID' in taskChanges) { - originalMessage.oldAssigneeAccountID = getTaskAssigneeAccountID(oldTask); - originalMessage.assigneeAccountID = taskChanges?.assigneeAccountID; - } - - return originalMessage; -} - /** * Check if original message is an object and can be used as a ChangeLog type * @param originalMessage @@ -3687,8 +3651,25 @@ function buildOptimisticUnHoldReportAction(created = DateUtils.getDBTime()): Opt }; } -function buildOptimisticEditedTaskReportAction(taskReport: Report, changes: Partial): OptimisticEditedTaskReportAction { - const originalMessage = getTaskEditedOriginalMessage(taskReport, changes); +function buildOptimisticEditedTaskFieldReportAction({title, description}: Task): OptimisticEditedTaskReportAction { + // We do not modify title & description in one request, so we need to create a different optimistic action for each field modification + let field = ''; + let value = ''; + if (title !== undefined) { + field = 'task title'; + value = title; + } else if (description !== undefined) { + field = 'description'; + value = description; + } + + let changelog = 'edited this task'; + if (field && value) { + changelog = `updated the ${field} to ${value}`; + } else if (field) { + changelog = `removed the ${field}`; + } + return { reportActionID: NumberUtils.rand64(), actionName: CONST.REPORT.ACTIONS.TYPE.TASKEDITED, @@ -3696,13 +3677,38 @@ function buildOptimisticEditedTaskReportAction(taskReport: Report, changes: Part actorAccountID: currentUserAccountID, message: [ { - // Currently, we are composing the message from the originalMessage and the message is not used in the App - text: 'You', - style: 'strong', + type: CONST.REPORT.MESSAGE.TYPE.COMMENT, + text: changelog, + html: changelog, + }, + ], + person: [ + { type: CONST.REPORT.MESSAGE.TYPE.TEXT, + style: 'strong', + text: getCurrentUserDisplayNameOrEmail(), + }, + ], + automatic: false, + avatar: getCurrentUserAvatarOrDefault(), + created: DateUtils.getDBTime(), + shouldShow: false, + }; +} + +function buildOptimisticChangedTaskAssigneeReportAction(assigneeAccountID: number): OptimisticEditedTaskReportAction { + return { + reportActionID: NumberUtils.rand64(), + actionName: CONST.REPORT.ACTIONS.TYPE.TASKEDITED, + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + actorAccountID: currentUserAccountID, + message: [ + { + type: CONST.REPORT.MESSAGE.TYPE.COMMENT, + text: `assigned to ${getDisplayNameForParticipant(assigneeAccountID)}`, + html: `assigned to `, }, ], - originalMessage, person: [ { type: CONST.REPORT.MESSAGE.TYPE.TEXT, @@ -5195,7 +5201,8 @@ export { buildOptimisticClosedReportAction, buildOptimisticCreatedReportAction, buildOptimisticRenamedRoomReportAction, - buildOptimisticEditedTaskReportAction, + buildOptimisticEditedTaskFieldReportAction, + buildOptimisticChangedTaskAssigneeReportAction, buildOptimisticIOUReport, buildOptimisticApprovedReportAction, buildOptimisticMovedReportAction, @@ -5272,7 +5279,6 @@ export { shouldDisableRename, hasSingleParticipant, getReportRecipientAccountIDs, - getTaskAssigneeAccountID, isOneOnOneChat, isPayer, goBackToDetailsPage, diff --git a/src/libs/TaskUtils.ts b/src/libs/TaskUtils.ts index d992b63bbb7f..81a079003d0e 100644 --- a/src/libs/TaskUtils.ts +++ b/src/libs/TaskUtils.ts @@ -3,12 +3,10 @@ import Onyx from 'react-native-onyx'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Report} from '@src/types/onyx'; -import type {OriginalMessageTaskEdited} from '@src/types/onyx/OriginalMessage'; import type {Message} from '@src/types/onyx/ReportAction'; import type ReportAction from '@src/types/onyx/ReportAction'; import * as CollectionUtils from './CollectionUtils'; import * as Localize from './Localize'; -import * as ReportUtils from './ReportUtils'; const allReports: Record = {}; Onyx.connect({ @@ -22,54 +20,6 @@ Onyx.connect({ }, }); -function buildMessageFragmentForValue(newValue: string | undefined, valueName: string): string { - const fieldName = valueName.toLowerCase(); - - if (!newValue) { - return Localize.translateLocal('task.messages.removedField', {fieldName}); - } - - return Localize.translateLocal('task.messages.updatedField', {fieldName, newValueToDisplay: newValue}); -} - -function getTaskEditedMessage(reportAction: OnyxEntry) { - if (reportAction?.actionName !== CONST.REPORT.ACTIONS.TYPE.TASKEDITED) { - return {text: ''}; - } - - const originalMessage = reportAction?.originalMessage as OriginalMessageTaskEdited['originalMessage'] | undefined; - if (!originalMessage) { - return { - text: reportAction?.message?.[0].text ?? '', - html: reportAction?.message?.[0].html, - }; - } - - const hasModifiedTitle = 'title' in originalMessage; - if (hasModifiedTitle) { - return { - text: buildMessageFragmentForValue(originalMessage.title, Localize.translateLocal('task.title')), - }; - } - - const hasModifiedDescription = 'description' in originalMessage; - if (hasModifiedDescription) { - return { - text: buildMessageFragmentForValue(originalMessage.description, Localize.translateLocal('task.description')), - }; - } - - const hasModifiedAssignee = 'assigneeAccountID' in originalMessage; - if (hasModifiedAssignee) { - return { - text: Localize.translateLocal('task.messages.updatedAssignee', {assignee: ReportUtils.getDisplayNameForParticipant(originalMessage.assigneeAccountID)}), - html: Localize.translateLocal('task.messages.updatedAssignee', {assignee: ``}), - }; - } - - return {text: ''}; -} - /** * Given the Task reportAction name, return the appropriate message to be displayed and copied to clipboard. */ @@ -82,7 +32,10 @@ function getTaskReportActionMessage(action: OnyxEntry): Pick) { API.write(WRITE_COMMANDS.REOPEN_TASK, parameters, {optimisticData, successData, failureData}); } -function editTask(report: OnyxTypes.Report, changes: Partial) { +function editTask(report: OnyxTypes.Report, {title, description}: OnyxTypes.Task) { // Create the EditedReportAction on the task - const editTaskReportAction = ReportUtils.buildOptimisticEditedTaskReportAction(report, changes); + const editTaskReportAction = ReportUtils.buildOptimisticEditedTaskFieldReportAction({title, description}); // Sometimes title or description is undefined, so we need to check for that, and we provide it to multiple functions - const reportName = (changes.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 = (changes.description ?? report.description ?? '').trim(); + const reportDescription = (description ?? report.description ?? '').trim(); const optimisticData: OnyxUpdate[] = [ { @@ -420,8 +420,8 @@ function editTask(report: OnyxTypes.Report, changes: Partial) { reportName, description: reportDescription, pendingFields: { - ...(changes.title && {reportName: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), - ...(changes.description && {description: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), + ...(title && {reportName: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), + ...(description && {description: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), }, errorFields: null, }, @@ -439,8 +439,8 @@ function editTask(report: OnyxTypes.Report, changes: Partial) { key: `${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, value: { pendingFields: { - ...(changes.title && {reportName: null}), - ...(changes.description && {description: null}), + ...(title && {reportName: null}), + ...(description && {description: null}), }, }, }, @@ -474,7 +474,7 @@ function editTask(report: OnyxTypes.Report, changes: Partial) { function editTaskAssignee(report: OnyxTypes.Report, ownerAccountID: number, assigneeEmail: string, assigneeAccountID = 0, assigneeChatReport: OnyxEntry = null) { // Create the EditedReportAction on the task - const editTaskReportAction = ReportUtils.buildOptimisticEditedTaskReportAction(report, {assigneeAccountID}); + const editTaskReportAction = ReportUtils.buildOptimisticChangedTaskAssigneeReportAction(assigneeAccountID); const reportName = report.reportName?.trim(); let assigneeChatReportOnyxData; @@ -889,6 +889,22 @@ function dismissModalAndClearOutTaskInfo() { clearOutTaskInfo(); } +/** + * Returns Task assignee accountID + */ +function getTaskAssigneeAccountID(taskReport: OnyxEntry): number | undefined { + if (!taskReport) { + return; + } + + if (taskReport.managerID) { + return taskReport.managerID; + } + + const reportAction = getParentReportAction(taskReport); + return reportAction.childManagerAccountID; +} + /** * Returns Task owner accountID */ @@ -909,7 +925,7 @@ function canModifyTask(taskReport: OnyxEntry, sessionAccountID return false; } - if (sessionAccountID === getTaskOwnerAccountID(taskReport) || sessionAccountID === ReportUtils.getTaskAssigneeAccountID(taskReport)) { + if (sessionAccountID === getTaskOwnerAccountID(taskReport) || sessionAccountID === getTaskAssigneeAccountID(taskReport)) { return true; } @@ -953,6 +969,7 @@ export { getShareDestination, deleteTask, dismissModalAndClearOutTaskInfo, + getTaskAssigneeAccountID, clearTaskErrors, canModifyTask, }; diff --git a/src/types/onyx/OriginalMessage.ts b/src/types/onyx/OriginalMessage.ts index 7d682c79c525..06c2d2e6abce 100644 --- a/src/types/onyx/OriginalMessage.ts +++ b/src/types/onyx/OriginalMessage.ts @@ -278,18 +278,6 @@ type OriginalMessageMoved = { }; }; -type OriginalMessageTaskEdited = { - actionName: typeof CONST.REPORT.ACTIONS.TYPE.TASKEDITED; - originalMessage: { - oldTitle?: string; - title?: string; - oldDescription?: string; - description?: string; - oldAssigneeAccountID?: number; - assigneeAccountID?: number; - }; -}; - type OriginalMessage = | OriginalMessageApproved | OriginalMessageIOU @@ -310,8 +298,7 @@ type OriginalMessage = | OriginalMessageReimbursementQueued | OriginalMessageReimbursementDequeued | OriginalMessageMoved - | OriginalMessageMarkedReimbursed - | OriginalMessageTaskEdited; + | OriginalMessageMarkedReimbursed; export default OriginalMessage; export type { @@ -332,7 +319,6 @@ export type { OriginalMessageChronosOOOList, OriginalMessageSource, OriginalMessageReimbursementDequeued, - OriginalMessageTaskEdited, DecisionName, PaymentMethodType, }; From e7031e4685850b38a88ea72ab7f09d122619aae6 Mon Sep 17 00:00:00 2001 From: Pavlo Tsimura Date: Thu, 7 Mar 2024 19:41:34 +0100 Subject: [PATCH 8/8] Fix TS error --- 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 624e42e0d9e6..27c7f3e36fd4 100644 --- a/src/libs/actions/Task.ts +++ b/src/libs/actions/Task.ts @@ -480,7 +480,7 @@ function editTaskAssignee( assigneeChatReport: OnyxEntry = null, ) { // Create the EditedReportAction on the task - const editTaskReportAction = ReportUtils.buildOptimisticChangedTaskAssigneeReportAction(assigneeAccountID); + const editTaskReportAction = ReportUtils.buildOptimisticChangedTaskAssigneeReportAction(assigneeAccountID ?? 0); const reportName = report.reportName?.trim(); let assigneeChatReportOnyxData;