From e9e72f907a65ddcffa7d344c447f53ad1516cb6a Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Wed, 6 Jul 2022 17:06:32 -1000 Subject: [PATCH 01/15] Remove setLocalLastRead Kill reportMaxSequenceNumbers Clean up some usages getUnreadActionCOunt remove getLastReadSequenceNumber Get rid of .then() calls in Report.js Remove ambiguous `updateReportActionMessage` method Remove last .then() in Report.js Fix lastMessageText bug Dont decrement count when handling deleted comment events from current user undo random/bad change Add Math.max() Update names in ReportActionsView Clear new marker when tapping floating message counter --- src/libs/DateUtils.js | 10 + src/libs/actions/Report.js | 206 +++++++++------------ src/libs/actions/ReportActions.js | 23 ++- src/pages/home/report/ReportActionsView.js | 38 ++-- 4 files changed, 133 insertions(+), 144 deletions(-) diff --git a/src/libs/DateUtils.js b/src/libs/DateUtils.js index 256c9938f805..c5f7de4079e7 100644 --- a/src/libs/DateUtils.js +++ b/src/libs/DateUtils.js @@ -148,6 +148,15 @@ function setTimezoneUpdated() { lastUpdatedTimezoneTime = moment(); } +/** + * Returns a timestamp since epoch in seconds + * + * @returns {Number} + */ +function getCurrentTimestamp() { + return Date.now() / 1000; +} + /** * @namespace DateUtils */ @@ -160,6 +169,7 @@ const DateUtils = { getCurrentTimezone, canUpdateTimezone, setTimezoneUpdated, + getCurrentTimestamp, }; export default DateUtils; diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 95165a316080..6fdbd065168f 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -59,24 +59,6 @@ const allReports = {}; let conciergeChatReportID; const typingWatchTimers = {}; -/** - * Map of the most recent sequenceNumber for a reports_* key in Onyx by reportID. - * - * There are several sources that can set the most recent reportAction's sequenceNumber for a report: - * - * - Fetching the report object - * - Fetching the report history - * - Optimistically creating a report action - * - Handling a report action via Pusher - * - * Those values are stored in reportMaxSequenceNumbers and treated as the main source of truth for each report's max - * sequenceNumber. - */ -const reportMaxSequenceNumbers = {}; - -// Keeps track of the last read for each report -const lastReadSequenceNumbers = {}; - // Map of optimistic report action IDs. These should be cleared when replaced by a recent fetch of report history // since we will then be up to date and any optimistic actions that are still waiting to be replaced can be removed. const optimisticReportActionIDs = {}; @@ -89,32 +71,25 @@ Onyx.connect({ }); /** - * Checks the report to see if there are any unread action items - * - * @param {Object} report + * @param {Number} reportID + * @param {Number} lastReadSequenceNumber + * @param {Number} maxSequenceNumber * @returns {Boolean} */ -function getUnreadActionCount(report) { - const lastReadSequenceNumber = lodashGet(report, [ - 'reportNameValuePairs', - `lastRead_${currentUserAccountID}`, - 'sequenceNumber', - ]); - - // Save the lastReadActionID locally so we can access this later - lastReadSequenceNumbers[report.reportID] = lastReadSequenceNumber; - - if (report.reportActionCount === 0) { +function getUnreadActionCount( + reportID, + lastReadSequenceNumber, + maxSequenceNumber, +) { + if (maxSequenceNumber === 0) { return 0; } if (!lastReadSequenceNumber) { - return report.reportActionCount; + return maxSequenceNumber; } - // There are unread items if the last one the user has read is less - // than the highest sequence number we have - const unreadActionCount = report.reportActionCount - lastReadSequenceNumber - ReportActions.getDeletedCommentsCount(report.reportID, lastReadSequenceNumber); + const unreadActionCount = maxSequenceNumber - lastReadSequenceNumber - ReportActions.getDeletedCommentsCount(reportID, lastReadSequenceNumber); return Math.max(0, unreadActionCount); } @@ -171,6 +146,11 @@ function getSimplifiedReportObject(report) { // Used for User Created Policy Rooms, will denote how access to a chat room is given among workspace members const visibility = lodashGet(report, ['reportNameValuePairs', 'visibility']); + const lastReadSequenceNumber = lodashGet(report, [ + 'reportNameValuePairs', + `lastRead_${currentUserAccountID}`, + 'sequenceNumber', + ]); return { reportID: report.reportID, @@ -178,7 +158,7 @@ function getSimplifiedReportObject(report) { chatType, ownerEmail: LoginUtils.getEmailWithoutMergedAccountPrefix(lodashGet(report, ['ownerEmail'], '')), policyID: lodashGet(report, ['reportNameValuePairs', 'expensify_policyID'], ''), - unreadActionCount: getUnreadActionCount(report), + unreadActionCount: getUnreadActionCount(report.reportID, lastReadSequenceNumber, report.reportActionCount), maxSequenceNumber: lodashGet(report, 'reportActionCount', 0), participants: getParticipantEmailsFromReport(report), isPinned: report.isPinned, @@ -187,6 +167,7 @@ function getSimplifiedReportObject(report) { `lastRead_${currentUserAccountID}`, 'timestamp', ], 0), + lastReadSequenceNumber, lastMessageTimestamp, lastMessageText: isLastMessageAttachment ? '[Attachment]' : lastMessageText, lastActorEmail, @@ -401,27 +382,6 @@ function setLocalIOUReportData(iouReportObject) { Onyx.merge(iouReportKey, iouReportObject); } -/** - * Update the lastRead actionID and timestamp in local memory and Onyx - * - * @param {Number} reportID - * @param {Number} lastReadSequenceNumber - */ -function setLocalLastRead(reportID, lastReadSequenceNumber) { - lastReadSequenceNumbers[reportID] = lastReadSequenceNumber; - const reportMaxSequenceNumber = reportMaxSequenceNumbers[reportID]; - - // Determine the number of unread actions by deducting the last read sequence from the total. If, for some reason, - // the last read sequence is higher than the actual last sequence, let's just assume all actions are read - const unreadActionCount = Math.max(reportMaxSequenceNumber - lastReadSequenceNumber - ReportActions.getDeletedCommentsCount(reportID, lastReadSequenceNumber), 0); - - // Update the report optimistically. - Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, { - unreadActionCount, - lastVisitedTimestamp: Date.now(), - }); -} - /** * Remove all optimistic actions from report actions and reset the optimisticReportActionsIDs array. We do this * to clear any stuck optimistic actions that have not be updated for whatever reason. @@ -510,28 +470,6 @@ function setNewMarkerPosition(reportID, sequenceNumber) { }); } -/** - * Updates a report action's message to be a new value. - * - * @param {Number} reportID - * @param {Number} sequenceNumber - * @param {Object} message - */ -function updateReportActionMessage(reportID, sequenceNumber, message) { - const actionToMerge = {}; - actionToMerge[sequenceNumber] = {message: [message]}; - Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, actionToMerge).then(() => { - // If the message is deleted, update the last read message and the unread counter - if (!message.html) { - setLocalLastRead(reportID, lastReadSequenceNumbers[reportID]); - } - - Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, { - lastMessageText: ReportActions.getLastVisibleMessageText(reportID), - }); - }); -} - /** * Get the private pusher channel name for a Report. * @@ -559,7 +497,25 @@ function subscribeToUserEvents() { // Live-update a report's actions when an 'edit comment' event is received. PusherUtils.subscribeToPrivateUserChannelEvent(Pusher.TYPE.REPORT_COMMENT_EDIT, currentUserAccountID, - pushJSON => updateReportActionMessage(pushJSON.reportID, pushJSON.sequenceNumber, pushJSON.message)); + ({reportID, sequenceNumber, message}) => { + const actionsToMerge = {}; + actionsToMerge[sequenceNumber] = {message: [message]}; + + // If someone besides the current user deleted an action and the sequenceNumber is greater than our last read we will decrement the unread count + const lastReadSequenceNumber = lodashGet(allReports, [reportID, 'lastReadSequenceNumber']); + const isFromCurrentUser = ReportActions.isFromCurrentUser(reportID, sequenceNumber, currentUserAccountID, actionsToMerge); + if (!message.html && !isFromCurrentUser && sequenceNumber > lastReadSequenceNumber) { + const currentUnreadActionCount = lodashGet(allReports, [reportID, 'unreadActionCount']); + Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, { + unreadActionCount: Math.max(currentUnreadActionCount - 1, 0), + }); + } + + Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, { + lastMessageText: ReportActions.getLastVisibleMessageText(reportID, actionsToMerge), + }); + Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, actionsToMerge); + }); } /** @@ -809,7 +765,7 @@ function fetchAllReports( // data processing by Onyx. const reportIDsWithMissingActions = _.chain(returnedReports) .map(report => report.reportID) - .filter(reportID => ReportActions.isReportMissingActions(reportID, reportMaxSequenceNumbers[reportID])) + .filter(reportID => ReportActions.isReportMissingActions(reportID, lodashGet(allReports, [reportID, 'maxSequenceNumber']))) .value(); // Once we have the reports that are missing actions we will find the intersection between the most @@ -858,7 +814,7 @@ function addComment(reportID, text, file) { const attachmentInfo = isAttachment ? file : {}; // The new sequence number will be one higher than the highest - const highestSequenceNumber = reportMaxSequenceNumbers[reportID] || 0; + const highestSequenceNumber = lodashGet(allReports, [reportID, 'maxSequenceNumber'], 0); const newSequenceNumber = highestSequenceNumber + 1; const htmlForNewComment = isAttachment ? 'Uploading Attachment...' : commentText; @@ -986,15 +942,6 @@ function addAttachment(reportID, file) { addComment(reportID, '', file); } -/** - * Get the last read sequence number for a report - * @param {String|Number} reportID - * @return {Number} - */ -function getLastReadSequenceNumber(reportID) { - return lastReadSequenceNumbers[reportID]; -} - /** * Deletes a comment from the report, basically sets it as empty string * @@ -1017,8 +964,17 @@ function deleteReportComment(reportID, reportAction) { ], }; - Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, reportActionsToMerge).then(() => { - setLocalLastRead(reportID, getLastReadSequenceNumber(reportID)); + // If the comment we are deleting is more recent than our last read comment we will update the unread count + if (sequenceNumber > lodashGet(allReports, [reportID, 'lastReadSequenceNumber'])) { + Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, { + unreadActionCount: Math.max(lodashGet(allReports, [reportID, 'unreadActionCount'], 0) - 1, 0), + }); + } + + // Optimistically update the report and reportActions + Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, reportActionsToMerge); + Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, { + lastMessageText: ReportActions.getLastVisibleMessageText(reportID, reportActionsToMerge), }); // Try to delete the comment by calling the API @@ -1038,9 +994,18 @@ function deleteReportComment(reportID, reportAction) { ...reportAction, message: oldMessage, }; - Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, reportActionsToMerge).then(() => { - setLocalLastRead(reportID, getLastReadSequenceNumber(reportID)); + + if (sequenceNumber > lodashGet(allReports, [reportID, 'lastReadSequenceNumber'])) { + Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, { + unreadActionCount: lodashGet(allReports, [reportID, 'unreadActionCount'], 0) + 1, + }); + } + + Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, { + lastMessageText: ReportActions.getLastVisibleMessageText(reportID, reportActionsToMerge), }); + + Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, reportActionsToMerge); }); } @@ -1063,18 +1028,19 @@ function updateLastReadActionID(reportID, sequenceNumber, manuallyMarked = false // If we aren't specifying a sequenceNumber and have no valid maxSequenceNumber for this report then we should not // update the last read. Most likely, we have just created the report and it has no comments. But we should err on // the side of caution and do nothing in this case. - if (_.isUndefined(sequenceNumber) - && (!reportMaxSequenceNumbers[reportID] && reportMaxSequenceNumbers[reportID] !== 0)) { + const reportMaxSequenceNumber = lodashGet(allReports, [reportID, 'maxSequenceNumber']); + if (_.isUndefined(sequenceNumber) && (!reportMaxSequenceNumber && reportMaxSequenceNumber !== 0)) { return; } - // Need to subtract 1 from sequenceNumber so that the "New" marker appears in the right spot (the last read - // action). If 1 isn't subtracted then the "New" marker appears one row below the action (the first unread action) - // Note: sequenceNumber can be 1 for the first message, so we can't use - // (sequenceNumber - 1) || reportMaxSequenceNumbers[reportID] because the first expression results in 0 which is falsy. - const lastReadSequenceNumber = _.isNumber(sequenceNumber) ? (sequenceNumber - 1) : reportMaxSequenceNumbers[reportID]; - - setLocalLastRead(reportID, lastReadSequenceNumber); + // When we are manually marking a report as unread we need to subtract 1 from sequenceNumber so that the "New" marker appears in the right spot. + // If 1 isn't subtracted then the "New" marker appears one row below the action (the first unread action). + const lastReadSequenceNumber = _.isNumber(sequenceNumber) ? (sequenceNumber - 1) : reportMaxSequenceNumber; + Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, { + unreadActionCount: getUnreadActionCount(reportID, lastReadSequenceNumber, reportMaxSequenceNumber), + lastVisitedTimestamp: DateUtils.getCurrentTimestamp(), + lastReadSequenceNumber, + }); // Mark the report as not having any unread items DeprecatedAPI.Report_UpdateLastRead({ @@ -1166,9 +1132,6 @@ function handleReportChanged(report) { fetchChatReportsByIDs([report.reportID]); } - // Store the max sequence number for each report - reportMaxSequenceNumbers[report.reportID] = report.maxSequenceNumber; - // Store optimistic actions IDs for each report optimisticReportActionIDs[report.reportID] = report.optimisticReportActionIDs; } @@ -1401,22 +1364,23 @@ function setIsComposerFullSize(reportID, isComposerFullSize) { * @param {Object} action */ function viewNewReportAction(reportID, action) { - const newMaxSequenceNumber = action.sequenceNumber; + const incomingSequenceNumber = action.sequenceNumber; const isFromCurrentUser = action.actorAccountID === currentUserAccountID; + const lastReadSequenceNumber = lodashGet(allReports, [reportID, 'lastReadSequenceNumber'], 0); + const updatedReportObject = {}; - // When handling an action from the current users we can assume that their - // last read actionID has been updated in the server but not necessarily reflected - // locally so we must first update it and then calculate the unread (which should be 0) + // When handling an action from the current user we can assume that their last read actionID has been updated in the server, but not necessarily reflected + // locally (e.g. could be from a different session) so we set their unreadActionCount to zero. If the action is from another user we will only update the + // unreadActionCount if the incoming sequenceNumber is higher than the last read for the user. if (isFromCurrentUser) { - setLocalLastRead(reportID, newMaxSequenceNumber); + updatedReportObject.unreadActionCount = 0; + updatedReportObject.lastVisitedTimestamp = DateUtils.getCurrentTimestamp(); + updatedReportObject.lastReadSequenceNumber = action.sequenceNumber; + } else if (incomingSequenceNumber > lastReadSequenceNumber) { + const currentUnreadActionCount = lodashGet(allReports, [reportID, 'unreadActionCount'], 0); + updatedReportObject.unreadActionCount = currentUnreadActionCount + 1; } - const updatedReportObject = { - // Use updated lastReadSequenceNumber, value may have been modified by setLocalLastRead - // Here deletedComments count does not include the new action being added. We can safely assume that newly received action is not deleted. - unreadActionCount: newMaxSequenceNumber - (lastReadSequenceNumbers[reportID] || 0) - ReportActions.getDeletedCommentsCount(reportID, lastReadSequenceNumbers[reportID] || 0), - }; - Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, updatedReportObject); // If chat report receives an action with IOU and we have an IOUReportID, update IOU object @@ -1463,8 +1427,7 @@ function viewNewReportAction(reportID, action) { // When a new message comes in, if the New marker is not already set (newMarkerSequenceNumber === 0), set the marker above the incoming message. const report = lodashGet(allReports, 'reportID', {}); if (lodashGet(report, 'newMarkerSequenceNumber', 0) === 0 && report.unreadActionCount > 0) { - const oldestUnreadSeq = (report.maxSequenceNumber - report.unreadActionCount) + 1; - setNewMarkerPosition(reportID, oldestUnreadSeq); + setNewMarkerPosition(reportID, report.lastReadSequenceNumber + 1); } Log.info('[LOCAL_NOTIFICATION] Creating notification'); @@ -1533,6 +1496,5 @@ export { fetchActionsWithLoadingState, createPolicyRoom, renameReport, - getLastReadSequenceNumber, setIsComposerFullSize, }; diff --git a/src/libs/actions/ReportActions.js b/src/libs/actions/ReportActions.js index a069aa6b115c..5653d3dbcad1 100644 --- a/src/libs/actions/ReportActions.js +++ b/src/libs/actions/ReportActions.js @@ -1,6 +1,7 @@ import _ from 'underscore'; import Onyx from 'react-native-onyx'; import lodashGet from 'lodash/get'; +import lodashMerge from 'lodash/merge'; import ExpensiMark from 'expensify-common/lib/ExpensiMark'; import ONYXKEYS from '../../ONYXKEYS'; import * as CollectionUtils from '../CollectionUtils'; @@ -101,22 +102,36 @@ function getDeletedCommentsCount(reportID, sequenceNumber) { /** * Get the message text for the last action that was not deleted * @param {Number} reportID + * @param {Object} [actionsToMerge] * @return {String} */ -function getLastVisibleMessageText(reportID) { +function getLastVisibleMessageText(reportID, actionsToMerge = {}) { const parser = new ExpensiMark(); - const lastMessageIndex = _.findLastIndex(reportActions[reportID], action => ( + const actions = _.toArray(lodashMerge({}, reportActions[reportID], actionsToMerge)); + const lastMessageIndex = _.findLastIndex(actions, action => ( !ReportActionsUtils.isDeletedAction(action) )); - const htmlText = lodashGet(reportActions, [reportID, lastMessageIndex, 'message', 0, 'html'], ''); + const htmlText = lodashGet(actions, [lastMessageIndex, 'message', 0, 'html'], ''); const messageText = parser.htmlToText(htmlText); - return ReportUtils.formatReportLastMessageText(messageText); } +/** + * @param {Number} reportID + * @param {Number} sequenceNumber + * @param {Number} currentUserAccountID + * @param {Object} [actionsToMerge] + * @returns {Boolean} + */ +function isFromCurrentUser(reportID, sequenceNumber, currentUserAccountID, actionsToMerge = {}) { + const action = lodashMerge({}, reportActions[reportID], actionsToMerge)[sequenceNumber]; + return action.actorAccountID === currentUserAccountID; +} + export { isReportMissingActions, dangerouslyGetReportActionsMaxSequenceNumber, getDeletedCommentsCount, getLastVisibleMessageText, + isFromCurrentUser, }; diff --git a/src/pages/home/report/ReportActionsView.js b/src/pages/home/report/ReportActionsView.js index 1e919028393f..4aecf582c363 100755 --- a/src/pages/home/report/ReportActionsView.js +++ b/src/pages/home/report/ReportActionsView.js @@ -115,13 +115,13 @@ class ReportActionsView extends React.Component { this.updateMessageCounterCount = this.updateMessageCounterCount.bind(this); this.loadMoreChats = this.loadMoreChats.bind(this); this.recordTimeToMeasureItemLayout = this.recordTimeToMeasureItemLayout.bind(this); - this.scrollToBottomAndUpdateLastRead = this.scrollToBottomAndUpdateLastRead.bind(this); + this.scrollToBottomAndMarkReportAsRead = this.scrollToBottomAndMarkReportAsRead.bind(this); this.updateNewMarkerAndMarkReadOnce = _.once(this.updateNewMarkerAndMarkRead.bind(this)); } componentDidMount() { this.appStateChangeListener = AppState.addEventListener('change', () => { - if (!Visibility.isVisible() || this.props.isDrawerOpen) { + if (!this.getIsReportFullyVisible()) { return; } @@ -215,9 +215,7 @@ class ReportActionsView extends React.Component { const currentLastSequenceNumber = lodashGet(CollectionUtils.lastItem(this.props.reportActions), 'sequenceNumber'); // Record the max action when window is visible and the sidebar is not covering the report view on a small screen - const isSidebarCoveringReportView = this.props.isSmallScreenWidth && this.props.isDrawerOpen; - const shouldRecordMaxAction = Visibility.isVisible() && !isSidebarCoveringReportView; - + const isReportFullyVisible = this.getIsReportFullyVisible(); const sidebarClosed = prevProps.isDrawerOpen && !this.props.isDrawerOpen; const screenSizeIncreased = prevProps.isSmallScreenWidth && !this.props.isSmallScreenWidth; const reportBecomeVisible = sidebarClosed || screenSizeIncreased; @@ -232,7 +230,7 @@ class ReportActionsView extends React.Component { // Only update the unread count when the floating message counter is visible // Otherwise counter will be shown on scrolling up from the bottom even if user have read those messages if (this.state.isFloatingMessageCounterVisible) { - this.updateMessageCounterCount(!shouldRecordMaxAction); + this.updateMessageCounterCount(!isReportFullyVisible); } // Show new floating message counter when there is a new message @@ -241,13 +239,13 @@ class ReportActionsView extends React.Component { // When the last action changes, record the max action // This will make the NEW marker line go away if you receive comments in the same chat you're looking at - if (shouldRecordMaxAction) { + if (isReportFullyVisible) { Report.updateLastReadActionID(this.props.reportID); } } // Update the new marker position and last read action when we are closing the sidebar or moving from a small to large screen size - if (shouldRecordMaxAction && reportBecomeVisible) { + if (isReportFullyVisible && reportBecomeVisible) { this.updateNewMarkerPosition(this.props.report.unreadActionCount); Report.updateLastReadActionID(this.props.reportID); } @@ -265,6 +263,14 @@ class ReportActionsView extends React.Component { Report.unsubscribeFromReportChannel(this.props.reportID); } + /** + * @returns {Boolean} + */ + getIsReportFullyVisible() { + const isSidebarCoveringReportView = this.props.isSmallScreenWidth && this.props.isDrawerOpen; + return Visibility.isVisible() && !isSidebarCoveringReportView; + } + fetchData() { Report.fetchActions(this.props.reportID); } @@ -294,14 +300,10 @@ class ReportActionsView extends React.Component { Report.fetchActionsWithLoadingState(this.props.reportID, offset); } - /** - * This function is triggered from the ref callback for the scrollview. That way it can be scrolled once all the - * items have been rendered. If the number of actions has changed since it was last rendered, then - * scroll the list to the end. As a report can contain non-message actions, we should confirm that list data exists. - */ - scrollToBottomAndUpdateLastRead() { + scrollToBottomAndMarkReportAsRead() { ReportScrollManager.scrollToBottom(); Report.updateLastReadActionID(this.props.reportID); + Report.setNewMarkerPosition(this.props.reportID, 0); } /** @@ -312,7 +314,7 @@ class ReportActionsView extends React.Component { // Since we want the New marker to remain in place even if newer messages come in, we set it once on mount. // We determine the last read action by deducting the number of unread actions from the total number. // Then, we add 1 because we want the New marker displayed over the oldest unread sequence. - const oldestUnreadSequenceNumber = unreadActionCount === 0 ? 0 : Report.getLastReadSequenceNumber(this.props.report.reportID) + 1; + const oldestUnreadSequenceNumber = unreadActionCount === 0 ? 0 : this.props.report.lastReadSequenceNumber + 1; Report.setNewMarkerPosition(this.props.reportID, oldestUnreadSequenceNumber); } @@ -351,8 +353,8 @@ class ReportActionsView extends React.Component { updateNewMarkerAndMarkRead() { this.updateNewMarkerPosition(this.props.report.unreadActionCount); - // Only mark as read if the report is open - if (!this.props.isDrawerOpen) { + // Only mark as read if the report is fully visible + if (this.getIsReportFullyVisible()) { Report.updateLastReadActionID(this.props.reportID); } } @@ -417,7 +419,7 @@ class ReportActionsView extends React.Component { Date: Fri, 8 Jul 2022 08:25:46 -1000 Subject: [PATCH 02/15] Default to 0 everywhere --- src/libs/actions/Report.js | 56 ++++++++++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 6fdbd065168f..00a44bbd594c 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -76,7 +76,7 @@ Onyx.connect({ * @param {Number} maxSequenceNumber * @returns {Boolean} */ -function getUnreadActionCount( +function calculateUnreadActionCount( reportID, lastReadSequenceNumber, maxSequenceNumber, @@ -93,6 +93,30 @@ function getUnreadActionCount( return Math.max(0, unreadActionCount); } +/** + * @param {Number} reportID + * @returns {Number} + */ +function getUnreadActionCount(reportID) { + return lodashGet(allReports, [reportID, 'unreadActionCount'], 0); +} + +/** + * @param {Number} reportID + * @returns {Number} + */ +function getLastReadSequenceNumber(reportID) { + return lodashGet(allReports, [reportID, 'lastReadSequenceNumber'], 0); +} + +/** + * @param {Number} reportID + * @returns {Number} + */ +function getMaxSequenceNumber(reportID) { + return lodashGet(allReports, [reportID, 'maxSequenceNumber'], 0); +} + /** * @param {Object} report * @return {String[]} @@ -158,7 +182,7 @@ function getSimplifiedReportObject(report) { chatType, ownerEmail: LoginUtils.getEmailWithoutMergedAccountPrefix(lodashGet(report, ['ownerEmail'], '')), policyID: lodashGet(report, ['reportNameValuePairs', 'expensify_policyID'], ''), - unreadActionCount: getUnreadActionCount(report.reportID, lastReadSequenceNumber, report.reportActionCount), + unreadActionCount: calculateUnreadActionCount(report.reportID, lastReadSequenceNumber, report.reportActionCount), maxSequenceNumber: lodashGet(report, 'reportActionCount', 0), participants: getParticipantEmailsFromReport(report), isPinned: report.isPinned, @@ -498,16 +522,19 @@ function subscribeToUserEvents() { PusherUtils.subscribeToPrivateUserChannelEvent(Pusher.TYPE.REPORT_COMMENT_EDIT, currentUserAccountID, ({reportID, sequenceNumber, message}) => { + // We only want the active client to process these events once + if (!ActiveClientManager.isClientTheLeader()) { + return; + } + const actionsToMerge = {}; actionsToMerge[sequenceNumber] = {message: [message]}; // If someone besides the current user deleted an action and the sequenceNumber is greater than our last read we will decrement the unread count - const lastReadSequenceNumber = lodashGet(allReports, [reportID, 'lastReadSequenceNumber']); const isFromCurrentUser = ReportActions.isFromCurrentUser(reportID, sequenceNumber, currentUserAccountID, actionsToMerge); - if (!message.html && !isFromCurrentUser && sequenceNumber > lastReadSequenceNumber) { - const currentUnreadActionCount = lodashGet(allReports, [reportID, 'unreadActionCount']); + if (!message.html && !isFromCurrentUser && sequenceNumber > getLastReadSequenceNumber(reportID)) { Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, { - unreadActionCount: Math.max(currentUnreadActionCount - 1, 0), + unreadActionCount: Math.max(getUnreadActionCount(reportID) - 1, 0), }); } @@ -814,7 +841,7 @@ function addComment(reportID, text, file) { const attachmentInfo = isAttachment ? file : {}; // The new sequence number will be one higher than the highest - const highestSequenceNumber = lodashGet(allReports, [reportID, 'maxSequenceNumber'], 0); + const highestSequenceNumber = getMaxSequenceNumber(reportID); const newSequenceNumber = highestSequenceNumber + 1; const htmlForNewComment = isAttachment ? 'Uploading Attachment...' : commentText; @@ -965,9 +992,9 @@ function deleteReportComment(reportID, reportAction) { }; // If the comment we are deleting is more recent than our last read comment we will update the unread count - if (sequenceNumber > lodashGet(allReports, [reportID, 'lastReadSequenceNumber'])) { + if (sequenceNumber > getLastReadSequenceNumber(reportID)) { Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, { - unreadActionCount: Math.max(lodashGet(allReports, [reportID, 'unreadActionCount'], 0) - 1, 0), + unreadActionCount: Math.max(getUnreadActionCount(reportID) - 1, 0), }); } @@ -995,9 +1022,9 @@ function deleteReportComment(reportID, reportAction) { message: oldMessage, }; - if (sequenceNumber > lodashGet(allReports, [reportID, 'lastReadSequenceNumber'])) { + if (sequenceNumber > getLastReadSequenceNumber(reportID)) { Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, { - unreadActionCount: lodashGet(allReports, [reportID, 'unreadActionCount'], 0) + 1, + unreadActionCount: getUnreadActionCount(reportID) + 1, }); } @@ -1037,7 +1064,7 @@ function updateLastReadActionID(reportID, sequenceNumber, manuallyMarked = false // If 1 isn't subtracted then the "New" marker appears one row below the action (the first unread action). const lastReadSequenceNumber = _.isNumber(sequenceNumber) ? (sequenceNumber - 1) : reportMaxSequenceNumber; Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, { - unreadActionCount: getUnreadActionCount(reportID, lastReadSequenceNumber, reportMaxSequenceNumber), + unreadActionCount: calculateUnreadActionCount(reportID, lastReadSequenceNumber, reportMaxSequenceNumber), lastVisitedTimestamp: DateUtils.getCurrentTimestamp(), lastReadSequenceNumber, }); @@ -1366,7 +1393,7 @@ function setIsComposerFullSize(reportID, isComposerFullSize) { function viewNewReportAction(reportID, action) { const incomingSequenceNumber = action.sequenceNumber; const isFromCurrentUser = action.actorAccountID === currentUserAccountID; - const lastReadSequenceNumber = lodashGet(allReports, [reportID, 'lastReadSequenceNumber'], 0); + const lastReadSequenceNumber = getLastReadSequenceNumber(reportID); const updatedReportObject = {}; // When handling an action from the current user we can assume that their last read actionID has been updated in the server, but not necessarily reflected @@ -1377,8 +1404,7 @@ function viewNewReportAction(reportID, action) { updatedReportObject.lastVisitedTimestamp = DateUtils.getCurrentTimestamp(); updatedReportObject.lastReadSequenceNumber = action.sequenceNumber; } else if (incomingSequenceNumber > lastReadSequenceNumber) { - const currentUnreadActionCount = lodashGet(allReports, [reportID, 'unreadActionCount'], 0); - updatedReportObject.unreadActionCount = currentUnreadActionCount + 1; + updatedReportObject.unreadActionCount = getUnreadActionCount(reportID) + 1; } Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, updatedReportObject); From ad07325cc33b728d86a659493f483a6a9f49cfd6 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Fri, 8 Jul 2022 08:31:19 -1000 Subject: [PATCH 03/15] add the why into the comment --- src/libs/actions/Report.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 00a44bbd594c..25ea6e0e87ed 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -522,7 +522,7 @@ function subscribeToUserEvents() { PusherUtils.subscribeToPrivateUserChannelEvent(Pusher.TYPE.REPORT_COMMENT_EDIT, currentUserAccountID, ({reportID, sequenceNumber, message}) => { - // We only want the active client to process these events once + // We only want the active client to process these events once otherwise multiple tabs would decrement the 'unreadActionCount' if (!ActiveClientManager.isClientTheLeader()) { return; } From 2fedb57d5dd11ff36cf2d96f95825470e96b823e Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Fri, 8 Jul 2022 10:23:51 -1000 Subject: [PATCH 04/15] Use moment.unix() instead of getCurrentTimestamp() --- src/libs/DateUtils.js | 10 ---------- src/libs/actions/Report.js | 4 ++-- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/src/libs/DateUtils.js b/src/libs/DateUtils.js index c5f7de4079e7..256c9938f805 100644 --- a/src/libs/DateUtils.js +++ b/src/libs/DateUtils.js @@ -148,15 +148,6 @@ function setTimezoneUpdated() { lastUpdatedTimezoneTime = moment(); } -/** - * Returns a timestamp since epoch in seconds - * - * @returns {Number} - */ -function getCurrentTimestamp() { - return Date.now() / 1000; -} - /** * @namespace DateUtils */ @@ -169,7 +160,6 @@ const DateUtils = { getCurrentTimezone, canUpdateTimezone, setTimezoneUpdated, - getCurrentTimestamp, }; export default DateUtils; diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 25ea6e0e87ed..9397462acf4b 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -1065,7 +1065,7 @@ function updateLastReadActionID(reportID, sequenceNumber, manuallyMarked = false const lastReadSequenceNumber = _.isNumber(sequenceNumber) ? (sequenceNumber - 1) : reportMaxSequenceNumber; Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, { unreadActionCount: calculateUnreadActionCount(reportID, lastReadSequenceNumber, reportMaxSequenceNumber), - lastVisitedTimestamp: DateUtils.getCurrentTimestamp(), + lastVisitedTimestamp: moment.unix(), lastReadSequenceNumber, }); @@ -1401,7 +1401,7 @@ function viewNewReportAction(reportID, action) { // unreadActionCount if the incoming sequenceNumber is higher than the last read for the user. if (isFromCurrentUser) { updatedReportObject.unreadActionCount = 0; - updatedReportObject.lastVisitedTimestamp = DateUtils.getCurrentTimestamp(); + updatedReportObject.lastVisitedTimestamp = moment.unix(); updatedReportObject.lastReadSequenceNumber = action.sequenceNumber; } else if (incomingSequenceNumber > lastReadSequenceNumber) { updatedReportObject.unreadActionCount = getUnreadActionCount(reportID) + 1; From 454e0681ff6d0254243dfcb28dc6447e32e3808a Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Fri, 8 Jul 2022 10:49:29 -1000 Subject: [PATCH 05/15] adding test --- tests/actions/ReportTest.js | 85 ++++++++++++++++++++++++++++++++++++- 1 file changed, 84 insertions(+), 1 deletion(-) diff --git a/tests/actions/ReportTest.js b/tests/actions/ReportTest.js index 99c2b0fb0e8e..4810c07f1ee1 100644 --- a/tests/actions/ReportTest.js +++ b/tests/actions/ReportTest.js @@ -15,6 +15,7 @@ import * as TestHelper from '../utils/TestHelper'; import Log from '../../src/libs/Log'; import * as PersistedRequests from '../../src/libs/actions/PersistedRequests'; import * as User from '../../src/libs/actions/User'; +import moment from 'moment'; describe('actions/Report', () => { beforeAll(() => { @@ -47,7 +48,7 @@ describe('actions/Report', () => { Pusher.unsubscribe(`${CONST.PUSHER.PRIVATE_USER_CHANNEL_PREFIX}1${CONFIG.PUSHER.SUFFIX}`); }); - it('should store a new report action in Onyx when reportComment event is handled via Pusher', () => { + it('should store a new report action in Onyx when onyxApiUpdate event is handled via Pusher', () => { global.fetch = TestHelper.getGlobalFetchMock(); const TEST_USER_ACCOUNT_ID = 1; @@ -212,4 +213,86 @@ describe('actions/Report', () => { expect(addCommentCalls.length).toBe(1); }); }); + + it('should be marked as unread when a new comment is added', () => { + let report; + Onyx.connect({ + key: `${ONYXKEYS.COLLECTION.REPORT}${1}`, + callback: val => report = val, + }); + + const USER_2_LOGIN = 'different-user@test.com'; + const REPORT_ID = 1; + const ACTION = { + actionName: CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT, + actorAccountID: 2, + actorEmail: USER_2_LOGIN, + automatic: false, + avatar: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/avatar_3.png', + message: [{type: 'COMMENT', html: 'Testing a comment', text: 'Testing a comment'}], + person: [{type: 'TEXT', style: 'strong', text: 'Test User'}], + sequenceNumber: 1, + shouldShow: true, + timestamp: moment.unix(), + }; + + return TestHelper.signInWithTestUser(1, 'test@test.com') + .then(() => { + // Given a test user that is subscribed to Pusher events + User.subscribeToUserEvents(); + return waitForPromisesToResolve(); + }) + .then(() => { + // When a Pusher event is handled for a new report comment + const channel = Pusher.getChannel(`${CONST.PUSHER.PRIVATE_USER_CHANNEL_PREFIX}1${CONFIG.PUSHER.SUFFIX}`); + channel.emit(Pusher.TYPE.ONYX_API_UPDATE, [ + { + onyxMethod: CONST.ONYX.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, + value: { + reportID: REPORT_ID, + maxSequenceNumber: 1, + notificationPreference: 'always', + lastMessageTimestamp: 0, + lastMessageText: 'Comment 1', + lastActorEmail: USER_2_LOGIN, + }, + }, + { + onyxMethod: CONST.ONYX.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${REPORT_ID}`, + value: { + 1: ACTION, + }, + }, + ]); + return waitForPromisesToResolve(); + }) + .then(() => { + // Then the report will have an unreadActionCount + expect(report.unreadActionCount).toBe(1); + + // When the user visits the report + Report.updateLastReadActionID(REPORT_ID); + return waitForPromisesToResolve(); + }) + .then(() => { + // The unreadActionCount will return to 0 + expect(report.unreadActionCount).toBe(0); + + // When the user manually marks a message as "unread" + Report.updateLastReadActionID(REPORT_ID, 1, true); + return waitForPromisesToResolve(); + }) + .then(() => { + // The unreadActionCount will increase and the new marker will be set correctly + expect(report.unreadActionCount).toBe(1); + + console.log(report); + + // If the user deletes a comment that is before the last read no change in unreadActionCount will occur + // If the user deletes a comment that is after the last read the unreadActionCount will decrease + // If the last message on the report is deleted. The lastMessageText will reflect the new last comment. + }); + }); }); From 618ca89160125c7e2686174305b05b1dd6c04bb3 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Fri, 8 Jul 2022 10:52:16 -1000 Subject: [PATCH 06/15] Fix moment.unix() --- src/libs/actions/Report.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 9397462acf4b..aae3756e0bec 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -1065,7 +1065,7 @@ function updateLastReadActionID(reportID, sequenceNumber, manuallyMarked = false const lastReadSequenceNumber = _.isNumber(sequenceNumber) ? (sequenceNumber - 1) : reportMaxSequenceNumber; Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, { unreadActionCount: calculateUnreadActionCount(reportID, lastReadSequenceNumber, reportMaxSequenceNumber), - lastVisitedTimestamp: moment.unix(), + lastVisitedTimestamp: moment().unix(), lastReadSequenceNumber, }); @@ -1401,7 +1401,7 @@ function viewNewReportAction(reportID, action) { // unreadActionCount if the incoming sequenceNumber is higher than the last read for the user. if (isFromCurrentUser) { updatedReportObject.unreadActionCount = 0; - updatedReportObject.lastVisitedTimestamp = moment.unix(); + updatedReportObject.lastVisitedTimestamp = moment().unix(); updatedReportObject.lastReadSequenceNumber = action.sequenceNumber; } else if (incomingSequenceNumber > lastReadSequenceNumber) { updatedReportObject.unreadActionCount = getUnreadActionCount(reportID) + 1; From bcff07ad6d542b92d43e48e5125fe30e817c45c4 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Fri, 8 Jul 2022 12:49:25 -1000 Subject: [PATCH 07/15] Add test fix broken things --- src/libs/actions/Report.js | 27 ++- src/libs/actions/ReportActions.js | 3 +- .../report/ContextMenu/ContextMenuActions.js | 1 - tests/actions/ReportTest.js | 162 ++++++++++++++++-- 4 files changed, 178 insertions(+), 15 deletions(-) diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 9397462acf4b..e2ca9dcfdc50 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -27,6 +27,7 @@ import Growl from '../Growl'; import * as Localize from '../Localize'; import PusherUtils from '../PusherUtils'; import DateUtils from '../DateUtils'; +import * as ReportActionsUtils from '../ReportActionsUtils'; let currentUserEmail; let currentUserAccountID; @@ -855,6 +856,8 @@ function addComment(reportID, text, file) { lastMessageTimestamp: moment().unix(), lastMessageText: ReportUtils.formatReportLastMessageText(textForNewComment), lastActorEmail: currentUserEmail, + unreadActionCount: 0, + lastReadSequenceNumber: newSequenceNumber, }; // Generate a clientID so we can save the optimistic action to storage with the clientID as key. Later, we will @@ -1065,10 +1068,14 @@ function updateLastReadActionID(reportID, sequenceNumber, manuallyMarked = false const lastReadSequenceNumber = _.isNumber(sequenceNumber) ? (sequenceNumber - 1) : reportMaxSequenceNumber; Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, { unreadActionCount: calculateUnreadActionCount(reportID, lastReadSequenceNumber, reportMaxSequenceNumber), - lastVisitedTimestamp: moment.unix(), + lastVisitedTimestamp: moment().unix(), lastReadSequenceNumber, }); + if (manuallyMarked) { + setNewMarkerPosition(reportID, sequenceNumber); + } + // Mark the report as not having any unread items DeprecatedAPI.Report_UpdateLastRead({ reportID, @@ -1401,7 +1408,7 @@ function viewNewReportAction(reportID, action) { // unreadActionCount if the incoming sequenceNumber is higher than the last read for the user. if (isFromCurrentUser) { updatedReportObject.unreadActionCount = 0; - updatedReportObject.lastVisitedTimestamp = moment.unix(); + updatedReportObject.lastVisitedTimestamp = moment().unix(); updatedReportObject.lastReadSequenceNumber = action.sequenceNumber; } else if (incomingSequenceNumber > lastReadSequenceNumber) { updatedReportObject.unreadActionCount = getUnreadActionCount(reportID) + 1; @@ -1466,6 +1473,8 @@ function viewNewReportAction(reportID, action) { }); } +// We are using this map to ensure actions are only handled once +const handledReportActions = {}; Onyx.connect({ key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, initWithStoredValues: false, @@ -1477,6 +1486,18 @@ Onyx.connect({ } _.each(actions, (action) => { + if (lodashGet(handledReportActions, [reportID, action.sequenceNumber])) { + return; + } + + if (ReportActionsUtils.isDeletedAction(action)) { + return; + } + + if (action.isLoading) { + return; + } + if (!action.timestamp) { return; } @@ -1487,6 +1508,8 @@ Onyx.connect({ } viewNewReportAction(reportID, action); + handledReportActions[reportID] = handledReportActions[reportID] || {}; + handledReportActions[reportID][action.sequenceNumber] = true; }); }, }); diff --git a/src/libs/actions/ReportActions.js b/src/libs/actions/ReportActions.js index 5653d3dbcad1..1ade96d76dc3 100644 --- a/src/libs/actions/ReportActions.js +++ b/src/libs/actions/ReportActions.js @@ -107,7 +107,8 @@ function getDeletedCommentsCount(reportID, sequenceNumber) { */ function getLastVisibleMessageText(reportID, actionsToMerge = {}) { const parser = new ExpensiMark(); - const actions = _.toArray(lodashMerge({}, reportActions[reportID], actionsToMerge)); + const existingReportActions = _.reduce(reportActions[reportID], (prev, curr) => ({...prev, [curr.sequenceNumber]: curr}), {}); + const actions = _.toArray(lodashMerge({}, existingReportActions, actionsToMerge)); const lastMessageIndex = _.findLastIndex(actions, action => ( !ReportActionsUtils.isDeletedAction(action) )); diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.js b/src/pages/home/report/ContextMenu/ContextMenuActions.js index e506d396110a..e9167bb95ee3 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.js +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.js @@ -140,7 +140,6 @@ export default [ shouldShow: type => type === CONTEXT_MENU_TYPES.REPORT_ACTION, onPress: (closePopover, {reportAction, reportID}) => { Report.updateLastReadActionID(reportID, reportAction.sequenceNumber, true); - Report.setNewMarkerPosition(reportID, reportAction.sequenceNumber); if (closePopover) { hideContextMenu(true, ReportActionComposeFocusManager.focus); } diff --git a/tests/actions/ReportTest.js b/tests/actions/ReportTest.js index 4810c07f1ee1..073643a9d66b 100644 --- a/tests/actions/ReportTest.js +++ b/tests/actions/ReportTest.js @@ -107,6 +107,8 @@ describe('actions/Report', () => { // We subscribed to the Pusher channel above and now we need to simulate a reportComment action // Pusher event so we can verify that action was handled correctly and merged into the reportActions. const channel = Pusher.getChannel(`${CONST.PUSHER.PRIVATE_USER_CHANNEL_PREFIX}1${CONFIG.PUSHER.SUFFIX}`); + const actionWithoutLoading = {...resultAction}; + delete actionWithoutLoading.isLoading; channel.emit(Pusher.TYPE.ONYX_API_UPDATE, [ { onyxMethod: 'merge', @@ -125,7 +127,7 @@ describe('actions/Report', () => { key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${REPORT_ID}`, value: { [clientID]: null, - [ACTION_ID]: _.without(resultAction, 'loading'), + [ACTION_ID]: actionWithoutLoading, }, }, ]); @@ -215,36 +217,52 @@ describe('actions/Report', () => { }); it('should be marked as unread when a new comment is added', () => { + const REPORT_ID = 1; + let report; Onyx.connect({ - key: `${ONYXKEYS.COLLECTION.REPORT}${1}`, + key: `${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, callback: val => report = val, }); + let reportActions; + Onyx.connect({ + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${REPORT_ID}`, + callback: val => reportActions = val, + }); + + const channel = Pusher.getChannel(`${CONST.PUSHER.PRIVATE_USER_CHANNEL_PREFIX}1${CONFIG.PUSHER.SUFFIX}`); const USER_2_LOGIN = 'different-user@test.com'; - const REPORT_ID = 1; const ACTION = { actionName: CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT, actorAccountID: 2, actorEmail: USER_2_LOGIN, automatic: false, avatar: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/avatar_3.png', - message: [{type: 'COMMENT', html: 'Testing a comment', text: 'Testing a comment'}], + message: [{type: 'COMMENT', html: 'Comment 1', text: 'Comment 1'}], person: [{type: 'TEXT', style: 'strong', text: 'Test User'}], sequenceNumber: 1, shouldShow: true, - timestamp: moment.unix(), + timestamp: moment().unix(), }; - return TestHelper.signInWithTestUser(1, 'test@test.com') + return Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, {reportName: 'Test', reportID: REPORT_ID}) + .then(() => TestHelper.signInWithTestUser(1, 'test@test.com')) .then(() => { // Given a test user that is subscribed to Pusher events User.subscribeToUserEvents(); return waitForPromisesToResolve(); }) + .then(() => TestHelper.fetchPersonalDetailsForTestUser(1, 'test@test.com', { + 'test@test.com': { + accountID: 1, + email: 'test@test.com', + firstName: 'Test', + lastName: 'User', + }, + })) .then(() => { // When a Pusher event is handled for a new report comment - const channel = Pusher.getChannel(`${CONST.PUSHER.PRIVATE_USER_CHANNEL_PREFIX}1${CONFIG.PUSHER.SUFFIX}`); channel.emit(Pusher.TYPE.ONYX_API_UPDATE, [ { onyxMethod: CONST.ONYX.METHOD.MERGE, @@ -256,6 +274,8 @@ describe('actions/Report', () => { lastMessageTimestamp: 0, lastMessageText: 'Comment 1', lastActorEmail: USER_2_LOGIN, + newMarkerSequenceNumber: 0, + lastReadSequenceNumber: 0, }, }, { @@ -287,12 +307,132 @@ describe('actions/Report', () => { .then(() => { // The unreadActionCount will increase and the new marker will be set correctly expect(report.unreadActionCount).toBe(1); + expect(report.newMarkerSequenceNumber).toBe(1); - console.log(report); + // When a new comment is added by the current user + Report.addComment(REPORT_ID, 'Current User Comment 1'); + return waitForPromisesToResolve(); + }) + .then(() => { + // The unreadActionCount should be 0 and the lastReadSequenceNumber incremented + expect(report.unreadActionCount).toBe(0); + expect(report.lastReadSequenceNumber).toBe(2); + expect(report.lastMessageText).toBe('Current User Comment 1'); + + // When another comment is added by the current user + Report.addComment(REPORT_ID, 'Current User Comment 2'); + return waitForPromisesToResolve(); + }) + .then(() => { + // The unreadActionCount should be 0 and the lastReadSequenceNumber incremented + expect(report.unreadActionCount).toBe(0); + expect(report.lastReadSequenceNumber).toBe(3); + expect(report.lastMessageText).toBe('Current User Comment 2'); - // If the user deletes a comment that is before the last read no change in unreadActionCount will occur - // If the user deletes a comment that is after the last read the unreadActionCount will decrease - // If the last message on the report is deleted. The lastMessageText will reflect the new last comment. + // When another comment is added by the current user + Report.addComment(REPORT_ID, 'Current User Comment 3'); + return waitForPromisesToResolve(); + }) + .then(() => { + // The unreadActionCount should be 0 and the lastReadSequenceNumber incremented + expect(report.unreadActionCount).toBe(0); + expect(report.lastReadSequenceNumber).toBe(4); + expect(report.lastMessageText).toBe('Current User Comment 3'); + + // When we emit the events for these pending created actions to update them to not pending + channel.emit(Pusher.TYPE.ONYX_API_UPDATE, [ + { + onyxMethod: CONST.ONYX.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, + value: { + reportID: REPORT_ID, + maxSequenceNumber: 4, + notificationPreference: 'always', + lastMessageTimestamp: 0, + lastMessageText: 'Current User Comment 3', + lastActorEmail: 'test@test.com', + lastReadSequenceNumber: 4, + }, + }, + { + onyxMethod: CONST.ONYX.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${REPORT_ID}`, + value: { + [_.toArray(reportActions)[1].clientID]: null, + [_.toArray(reportActions)[2].clientID]: null, + [_.toArray(reportActions)[3].clientID]: null, + 2: { + actionName: CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT, + actorAccountID: 1, + actorEmail: 'test@test.com', + automatic: false, + avatar: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/avatar_3.png', + message: [{type: 'COMMENT', html: 'Current User Comment 1', text: 'Current User Comment 1'}], + person: [{type: 'TEXT', style: 'strong', text: 'Test User'}], + sequenceNumber: 2, + shouldShow: true, + timestamp: moment().unix(), + reportActionID: 'derp', + }, + 3: { + actionName: CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT, + actorAccountID: 1, + actorEmail: 'test@test.com', + automatic: false, + avatar: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/avatar_3.png', + message: [{type: 'COMMENT', html: 'Current User Comment 2', text: 'Current User Comment 2'}], + person: [{type: 'TEXT', style: 'strong', text: 'Test User'}], + sequenceNumber: 3, + shouldShow: true, + timestamp: moment().unix(), + reportActionID: 'derp', + }, + 4: { + actionName: CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT, + actorAccountID: 1, + actorEmail: 'test@test.com', + automatic: false, + avatar: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/avatar_3.png', + message: [{type: 'COMMENT', html: 'Current User Comment 3', text: 'Current User Comment 3'}], + person: [{type: 'TEXT', style: 'strong', text: 'Test User'}], + sequenceNumber: 4, + shouldShow: true, + timestamp: moment().unix(), + reportActionID: 'derp', + }, + }, + }, + ]); + + return waitForPromisesToResolve(); + }) + .then(() => { + // If the user deletes a comment that is before the last read + Report.deleteReportComment(REPORT_ID, reportActions[2]); + return waitForPromisesToResolve(); + }) + .then(() => { + // Then no change will occur + expect(report.lastReadSequenceNumber).toBe(4); + expect(report.unreadActionCount).toBe(0); + + // When the user manually marks a message as "unread" + Report.updateLastReadActionID(REPORT_ID, 3, true); + return waitForPromisesToResolve(); + }) + .then(() => { + // Then we should expect the unreadActionCount to be updated + expect(report.unreadActionCount).toBe(2); + expect(report.lastReadSequenceNumber).toBe(2); + expect(report.newMarkerSequenceNumber).toBe(3); + + // If the user deletes the last comment after the last read the unreadActionCount will decrease and the lastMessageText will reflect the new last comment + Report.deleteReportComment(REPORT_ID, reportActions[4]); + return waitForPromisesToResolve(); + }) + .then(() => { + expect(report.unreadActionCount).toBe(1); + expect(report.lastMessageText).toBe('Current User Comment 2'); }); }); }); From b0bac6a8c090063358912fbdf4509cce87f61fec Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Fri, 8 Jul 2022 12:59:22 -1000 Subject: [PATCH 08/15] use indexBy --- src/libs/actions/ReportActions.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/ReportActions.js b/src/libs/actions/ReportActions.js index 1ade96d76dc3..8b87ccb8480f 100644 --- a/src/libs/actions/ReportActions.js +++ b/src/libs/actions/ReportActions.js @@ -107,7 +107,7 @@ function getDeletedCommentsCount(reportID, sequenceNumber) { */ function getLastVisibleMessageText(reportID, actionsToMerge = {}) { const parser = new ExpensiMark(); - const existingReportActions = _.reduce(reportActions[reportID], (prev, curr) => ({...prev, [curr.sequenceNumber]: curr}), {}); + const existingReportActions = _.indexBy(reportActions[reportID], 'sequenceNumber'); const actions = _.toArray(lodashMerge({}, existingReportActions, actionsToMerge)); const lastMessageIndex = _.findLastIndex(actions, action => ( !ReportActionsUtils.isDeletedAction(action) @@ -125,7 +125,8 @@ function getLastVisibleMessageText(reportID, actionsToMerge = {}) { * @returns {Boolean} */ function isFromCurrentUser(reportID, sequenceNumber, currentUserAccountID, actionsToMerge = {}) { - const action = lodashMerge({}, reportActions[reportID], actionsToMerge)[sequenceNumber]; + const existingReportActions = _.indexBy(reportActions[reportID], 'sequenceNumber'); + const action = lodashMerge({}, existingReportActions, actionsToMerge)[sequenceNumber]; return action.actorAccountID === currentUserAccountID; } From e903cbe4bc05495b70e59233cbe4df5ff628c946 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Fri, 8 Jul 2022 13:06:02 -1000 Subject: [PATCH 09/15] DRY up test --- tests/actions/ReportTest.js | 82 ++++++++++++++++--------------------- 1 file changed, 36 insertions(+), 46 deletions(-) diff --git a/tests/actions/ReportTest.js b/tests/actions/ReportTest.js index 073643a9d66b..05f671210748 100644 --- a/tests/actions/ReportTest.js +++ b/tests/actions/ReportTest.js @@ -216,7 +216,7 @@ describe('actions/Report', () => { }); }); - it('should be marked as unread when a new comment is added', () => { + it('should be updated correctly when new comments are added, deleted or marked as unread', () => { const REPORT_ID = 1; let report; @@ -232,31 +232,22 @@ describe('actions/Report', () => { }); const channel = Pusher.getChannel(`${CONST.PUSHER.PRIVATE_USER_CHANNEL_PREFIX}1${CONFIG.PUSHER.SUFFIX}`); + const USER_1_LOGIN = 'user@test.com'; + const USER_1_ACCOUNT_ID = 1; const USER_2_LOGIN = 'different-user@test.com'; - const ACTION = { - actionName: CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT, - actorAccountID: 2, - actorEmail: USER_2_LOGIN, - automatic: false, - avatar: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/avatar_3.png', - message: [{type: 'COMMENT', html: 'Comment 1', text: 'Comment 1'}], - person: [{type: 'TEXT', style: 'strong', text: 'Test User'}], - sequenceNumber: 1, - shouldShow: true, - timestamp: moment().unix(), - }; + const USER_2_ACCOUNT_ID = 2; return Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, {reportName: 'Test', reportID: REPORT_ID}) - .then(() => TestHelper.signInWithTestUser(1, 'test@test.com')) + .then(() => TestHelper.signInWithTestUser(USER_1_ACCOUNT_ID, USER_1_LOGIN)) .then(() => { // Given a test user that is subscribed to Pusher events User.subscribeToUserEvents(); return waitForPromisesToResolve(); }) - .then(() => TestHelper.fetchPersonalDetailsForTestUser(1, 'test@test.com', { - 'test@test.com': { - accountID: 1, - email: 'test@test.com', + .then(() => TestHelper.fetchPersonalDetailsForTestUser(USER_1_ACCOUNT_ID, USER_1_LOGIN, { + [USER_1_LOGIN]: { + accountID: USER_1_ACCOUNT_ID, + email: USER_1_LOGIN, firstName: 'Test', lastName: 'User', }, @@ -282,7 +273,18 @@ describe('actions/Report', () => { onyxMethod: CONST.ONYX.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${REPORT_ID}`, value: { - 1: ACTION, + 1: { + actionName: CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT, + actorAccountID: USER_2_ACCOUNT_ID, + actorEmail: USER_2_LOGIN, + automatic: false, + avatar: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/avatar_3.png', + message: [{type: 'COMMENT', html: 'Comment 1', text: 'Comment 1'}], + person: [{type: 'TEXT', style: 'strong', text: 'Test User'}], + sequenceNumber: 1, + shouldShow: true, + timestamp: moment().unix(), + }, }, }, ]); @@ -339,6 +341,18 @@ describe('actions/Report', () => { expect(report.lastReadSequenceNumber).toBe(4); expect(report.lastMessageText).toBe('Current User Comment 3'); + const USER_1_BASE_ACTION = { + actionName: CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT, + actorAccountID: USER_1_ACCOUNT_ID, + actorEmail: USER_1_LOGIN, + automatic: false, + avatar: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/avatar_3.png', + person: [{type: 'TEXT', style: 'strong', text: 'Test User'}], + shouldShow: true, + timestamp: moment().unix(), + reportActionID: 'derp', + }; + // When we emit the events for these pending created actions to update them to not pending channel.emit(Pusher.TYPE.ONYX_API_UPDATE, [ { @@ -362,43 +376,19 @@ describe('actions/Report', () => { [_.toArray(reportActions)[2].clientID]: null, [_.toArray(reportActions)[3].clientID]: null, 2: { - actionName: CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT, - actorAccountID: 1, - actorEmail: 'test@test.com', - automatic: false, - avatar: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/avatar_3.png', + ...USER_1_BASE_ACTION, message: [{type: 'COMMENT', html: 'Current User Comment 1', text: 'Current User Comment 1'}], - person: [{type: 'TEXT', style: 'strong', text: 'Test User'}], sequenceNumber: 2, - shouldShow: true, - timestamp: moment().unix(), - reportActionID: 'derp', }, 3: { - actionName: CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT, - actorAccountID: 1, - actorEmail: 'test@test.com', - automatic: false, - avatar: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/avatar_3.png', + ...USER_1_BASE_ACTION, message: [{type: 'COMMENT', html: 'Current User Comment 2', text: 'Current User Comment 2'}], - person: [{type: 'TEXT', style: 'strong', text: 'Test User'}], sequenceNumber: 3, - shouldShow: true, - timestamp: moment().unix(), - reportActionID: 'derp', }, 4: { - actionName: CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT, - actorAccountID: 1, - actorEmail: 'test@test.com', - automatic: false, - avatar: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/avatar_3.png', + ...USER_1_BASE_ACTION, message: [{type: 'COMMENT', html: 'Current User Comment 3', text: 'Current User Comment 3'}], - person: [{type: 'TEXT', style: 'strong', text: 'Test User'}], sequenceNumber: 4, - shouldShow: true, - timestamp: moment().unix(), - reportActionID: 'derp', }, }, }, From aecb8a6f4bb9bed785b18b520681728234ce555b Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Fri, 8 Jul 2022 13:11:32 -1000 Subject: [PATCH 10/15] fix import order --- tests/actions/ReportTest.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/actions/ReportTest.js b/tests/actions/ReportTest.js index 05f671210748..05b35067b2b2 100644 --- a/tests/actions/ReportTest.js +++ b/tests/actions/ReportTest.js @@ -1,6 +1,7 @@ import _ from 'underscore'; import Onyx from 'react-native-onyx'; import lodashGet from 'lodash/get'; +import moment from 'moment'; import { beforeEach, beforeAll, afterEach, jest, describe, it, expect, } from '@jest/globals'; @@ -15,7 +16,6 @@ import * as TestHelper from '../utils/TestHelper'; import Log from '../../src/libs/Log'; import * as PersistedRequests from '../../src/libs/actions/PersistedRequests'; import * as User from '../../src/libs/actions/User'; -import moment from 'moment'; describe('actions/Report', () => { beforeAll(() => { From 1d9b1f44190edd06e19d0a4ac089cc96543b8c2d Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Fri, 8 Jul 2022 13:26:49 -1000 Subject: [PATCH 11/15] update comment and test --- src/libs/actions/Report.js | 1 + tests/actions/ReportTest.js | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index e2ca9dcfdc50..96b830f6391f 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -532,6 +532,7 @@ function subscribeToUserEvents() { actionsToMerge[sequenceNumber] = {message: [message]}; // If someone besides the current user deleted an action and the sequenceNumber is greater than our last read we will decrement the unread count + // we skip this for the current user because we should already have decremented the count optimistically when they deleted the comment. const isFromCurrentUser = ReportActions.isFromCurrentUser(reportID, sequenceNumber, currentUserAccountID, actionsToMerge); if (!message.html && !isFromCurrentUser && sequenceNumber > getLastReadSequenceNumber(reportID)) { Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, { diff --git a/tests/actions/ReportTest.js b/tests/actions/ReportTest.js index 05b35067b2b2..c5b2db8be9c2 100644 --- a/tests/actions/ReportTest.js +++ b/tests/actions/ReportTest.js @@ -231,12 +231,11 @@ describe('actions/Report', () => { callback: val => reportActions = val, }); - const channel = Pusher.getChannel(`${CONST.PUSHER.PRIVATE_USER_CHANNEL_PREFIX}1${CONFIG.PUSHER.SUFFIX}`); const USER_1_LOGIN = 'user@test.com'; const USER_1_ACCOUNT_ID = 1; const USER_2_LOGIN = 'different-user@test.com'; const USER_2_ACCOUNT_ID = 2; - + const channel = Pusher.getChannel(`${CONST.PUSHER.PRIVATE_USER_CHANNEL_PREFIX}${USER_1_ACCOUNT_ID}${CONFIG.PUSHER.SUFFIX}`); return Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, {reportName: 'Test', reportID: REPORT_ID}) .then(() => TestHelper.signInWithTestUser(USER_1_ACCOUNT_ID, USER_1_LOGIN)) .then(() => { From b15ed715d6a257b336543b7c3e9c9c02bc2d0a37 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Fri, 8 Jul 2022 15:22:28 -1000 Subject: [PATCH 12/15] Use valueOf() not unix() for lastVisitedTimestamp --- src/libs/actions/Report.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 96b830f6391f..6f4a660fa341 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -1069,7 +1069,7 @@ function updateLastReadActionID(reportID, sequenceNumber, manuallyMarked = false const lastReadSequenceNumber = _.isNumber(sequenceNumber) ? (sequenceNumber - 1) : reportMaxSequenceNumber; Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, { unreadActionCount: calculateUnreadActionCount(reportID, lastReadSequenceNumber, reportMaxSequenceNumber), - lastVisitedTimestamp: moment().unix(), + lastVisitedTimestamp: moment().valueOf(), lastReadSequenceNumber, }); @@ -1409,7 +1409,7 @@ function viewNewReportAction(reportID, action) { // unreadActionCount if the incoming sequenceNumber is higher than the last read for the user. if (isFromCurrentUser) { updatedReportObject.unreadActionCount = 0; - updatedReportObject.lastVisitedTimestamp = moment().unix(); + updatedReportObject.lastVisitedTimestamp = moment().valueOf(); updatedReportObject.lastReadSequenceNumber = action.sequenceNumber; } else if (incomingSequenceNumber > lastReadSequenceNumber) { updatedReportObject.unreadActionCount = getUnreadActionCount(reportID) + 1; From cc195225aff3e666598842ef2cb8d0a859d23c74 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Fri, 8 Jul 2022 15:49:39 -1000 Subject: [PATCH 13/15] Update Onyx constants. Fix new marker logic when marking comment as unread --- src/libs/actions/Report.js | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 3f48a3eef1b4..a5377f791ec3 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -1047,7 +1047,7 @@ function openReport(reportID) { }, { optimisticData: [{ - onyxMethod: 'merge', + onyxMethod: CONST.ONYX.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, value: { lastReadSequenceNumber: sequenceNumber, @@ -1091,6 +1091,7 @@ function readNewestAction(reportID) { */ function markCommentAsUnread(reportID, sequenceNumber) { const maxSequenceNumber = getMaxSequenceNumber(reportID); + const newLastReadSequenceNumber = sequenceNumber - 1; API.write('MarkCommentAsUnread', { reportID, @@ -1098,13 +1099,13 @@ function markCommentAsUnread(reportID, sequenceNumber) { }, { optimisticData: [{ - onyxMethod: 'merge', + onyxMethod: CONST.ONYX.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, value: { newMarkerSequenceNumber: sequenceNumber, - lastReadSequenceNumber: sequenceNumber, + lastReadSequenceNumber: newLastReadSequenceNumber, lastVisitedTimestamp: Date.now(), - unreadActionCount: calculateUnreadActionCount(reportID, sequenceNumber, maxSequenceNumber), + unreadActionCount: calculateUnreadActionCount(reportID, newLastReadSequenceNumber, maxSequenceNumber), }, }], }); @@ -1121,7 +1122,7 @@ function togglePinnedState(report) { // Optimistically pin/unpin the report before we send out the command const optimisticData = [ { - onyxMethod: 'merge', + onyxMethod: CONST.ONYX.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${report.reportID}`, value: {isPinned: pinnedValue}, }, From 0b51cfca6020c4b9fed37900677dddfcff9aab29 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Fri, 8 Jul 2022 15:50:54 -1000 Subject: [PATCH 14/15] Remove Report_UpdateLastRead --- src/libs/deprecatedAPI.js | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/libs/deprecatedAPI.js b/src/libs/deprecatedAPI.js index 24a9c3e9c73c..c08340bced61 100644 --- a/src/libs/deprecatedAPI.js +++ b/src/libs/deprecatedAPI.js @@ -356,18 +356,6 @@ function Report_EditComment(parameters) { return Network.post(commandName, parameters); } -/** - * @param {Object} parameters - * @param {Number} parameters.reportID - * @param {Number} parameters.sequenceNumber - * @returns {Promise} - */ -function Report_UpdateLastRead(parameters) { - const commandName = 'Report_UpdateLastRead'; - requireParameters(['reportID', 'sequenceNumber'], parameters, commandName); - return Network.post(commandName, parameters); -} - /** * @param {Object} parameters * @param {String} parameters.email @@ -942,7 +930,6 @@ export { Report_GetHistory, Report_TogglePinned, Report_EditComment, - Report_UpdateLastRead, ResendValidateCode, ResetPassword, SetNameValuePair, From d721623731a4b6741aad899ecff03eaa13667114 Mon Sep 17 00:00:00 2001 From: Marc Glasser Date: Fri, 8 Jul 2022 16:02:43 -1000 Subject: [PATCH 15/15] command is MarkAsUnread not MarkCommentAsUnread --- src/libs/actions/Report.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index a5377f791ec3..05924321d313 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -1092,7 +1092,7 @@ function readNewestAction(reportID) { function markCommentAsUnread(reportID, sequenceNumber) { const maxSequenceNumber = getMaxSequenceNumber(reportID); const newLastReadSequenceNumber = sequenceNumber - 1; - API.write('MarkCommentAsUnread', + API.write('MarkAsUnread', { reportID, sequenceNumber,