Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[No QA] Report_AddComment Refactor Part 1 #9328

Merged
merged 4 commits into from
Jun 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
201 changes: 114 additions & 87 deletions src/libs/actions/Report.js
Original file line number Diff line number Diff line change
Expand Up @@ -591,42 +591,20 @@ function updateReportWithNewAction(
reportAction,
notificationPreference = CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS,
) {
const newMaxSequenceNumber = reportAction.sequenceNumber;
const isFromCurrentUser = reportAction.actorAccountID === currentUserAccountID;
const initialLastReadSequenceNumber = lastReadSequenceNumbers[reportID] || 0;
const messageText = reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.RENAMED
? lodashGet(reportAction, 'originalMessage.html', '')
: lodashGet(reportAction, ['message', 0, 'text'], '');

// 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)
if (isFromCurrentUser) {
setLocalLastRead(reportID, newMaxSequenceNumber);
}

let messageText = lodashGet(reportAction, ['message', 0, 'text'], '');
if (reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.RENAMED) {
messageText = lodashGet(reportAction, 'originalMessage.html', '');
}

// Always merge the reportID into Onyx
// If the report doesn't exist in Onyx yet, then all the rest of the data will be filled out
// by handleReportChanged
const updatedReportObject = {
// Always merge the reportID into Onyx. If the report doesn't exist in Onyx yet, then all the rest of the data will be filled out by handleReportChanged
reportID,

// 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),
maxSequenceNumber: reportAction.sequenceNumber,
notificationPreference,
lastMessageTimestamp: reportAction.timestamp,
lastMessageText: ReportUtils.formatReportLastMessageText(messageText),
lastActorEmail: reportAction.actorEmail,
};

// If the report action from pusher is a higher sequence number than we know about (meaning it has come from
// a chat participant in another application), then the last message text and author needs to be updated as well
if (newMaxSequenceNumber > initialLastReadSequenceNumber) {
updatedReportObject.lastMessageTimestamp = reportAction.timestamp;
updatedReportObject.lastMessageText = ReportUtils.formatReportLastMessageText(messageText);
updatedReportObject.lastActorEmail = reportAction.actorEmail;
}

Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, updatedReportObject);

const reportActionsToMerge = {};
Expand All @@ -644,63 +622,6 @@ function updateReportWithNewAction(
};

Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, reportActionsToMerge);

// If chat report receives an action with IOU and we have an IOUReportID, update IOU object
if (reportAction.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && reportAction.originalMessage.IOUReportID) {
const iouReportID = reportAction.originalMessage.IOUReportID;

// If the IOUDetails object exists we are in the Send Money flow, and we should not fetch and update the chatReport
// as this would overwrite any existing IOUs. For all other cases we must update the chatReport with the iouReportID as
// if we don't, new IOUs would not be displayed and paid IOUs would still show as unpaid.
if (reportAction.originalMessage.IOUDetails === undefined) {
fetchIOUReportByIDAndUpdateChatReport(iouReportID, reportID);
}
}

if (!ActiveClientManager.isClientTheLeader()) {
Log.info('[LOCAL_NOTIFICATION] Skipping notification because this client is not the leader');
return;
}

// We don't want to send a local notification if the user preference is daily or mute
if (notificationPreference === 'mute' || notificationPreference === 'daily') {
// eslint-disable-next-line max-len
Log.info(`[LOCAL_NOTIFICATION] No notification because user preference is to be notified: ${notificationPreference}`);
return;
}

// If this comment is from the current user we don't want to parrot whatever they wrote back to them.
if (isFromCurrentUser) {
Log.info('[LOCAL_NOTIFICATION] No notification because comment is from the currently logged in user');
return;
}

// If we are currently viewing this report do not show a notification.
if (reportID === lastViewedReportID && Visibility.isVisible()) {
Log.info('[LOCAL_NOTIFICATION] No notification because it was a comment for the current report');
return;
}

// If the comment came from Concierge let's not show a notification since we already show one for expensify.com
if (lodashGet(reportAction, 'actorEmail') === CONST.EMAIL.CONCIERGE) {
return;
}

// When a new message comes in, if the New marker is not already set (newMarkerSequenceNumber === 0), set the
// marker above the incoming message.
if (lodashGet(allReports, [reportID, 'newMarkerSequenceNumber'], 0) === 0
&& updatedReportObject.unreadActionCount > 0) {
const oldestUnreadSeq = (updatedReportObject.maxSequenceNumber - updatedReportObject.unreadActionCount) + 1;
setNewMarkerPosition(reportID, oldestUnreadSeq);
}
Log.info('[LOCAL_NOTIFICATION] Creating notification');
LocalNotification.showCommentNotification({
reportAction,
onClick: () => {
// Navigate to this report onClick
Navigation.navigate(ROUTES.getReportRoute(reportID));
},
});
}

/**
Expand Down Expand Up @@ -1543,6 +1464,112 @@ function renameReport(reportID, reportName) {
.finally(() => Onyx.set(ONYXKEYS.IS_LOADING_RENAME_POLICY_ROOM, false));
}

/**
* @param {Number} reportID
* @param {Object} action
*/
function viewNewReportAction(reportID, action) {
const newMaxSequenceNumber = action.sequenceNumber;
const isFromCurrentUser = action.actorAccountID === currentUserAccountID;

// 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)
if (isFromCurrentUser) {
setLocalLastRead(reportID, newMaxSequenceNumber);
}

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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking from an offline perspective. Let's say I'm offline and eventually I receive the new report action. If the 10-second check returns early we won't run this code and thus won't update the unreadActionCount, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you are offline then you can't get any Pusher updates at all. There is no "eventually" - you missed it and it's never coming.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you're asking what happens when we come back online - then we would fetch the reports which should update the unreadActionCount for any reports. If you manage to miss a report action update because you're offline and then come back online before the 10 second window - I guess you'd see a notification - which seems alright.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah okay, thanks for clarifying!


// If chat report receives an action with IOU and we have an IOUReportID, update IOU object
if (action.actionName === CONST.REPORT.ACTIONS.TYPE.IOU && action.originalMessage.IOUReportID) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar comment for this logic as for above.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the answer is the same here. If you miss this update then we just need to have the "reconnection" callbacks account for this data. All the IOU reports should ideally be sent whenever we are "reconnecting".

const iouReportID = action.originalMessage.IOUReportID;

// If the IOUDetails object exists we are in the Send Money flow, and we should not fetch and update the chatReport
// as this would overwrite any existing IOUs. For all other cases we must update the chatReport with the iouReportID as
// if we don't, new IOUs would not be displayed and paid IOUs would still show as unpaid.
if (action.originalMessage.IOUDetails === undefined) {
fetchIOUReportByIDAndUpdateChatReport(iouReportID, reportID);
}
}

const notificationPreference = lodashGet(allReports, [reportID, 'notificationPreference'], CONST.REPORT.NOTIFICATION_PREFERENCE.ALWAYS);
if (!ActiveClientManager.isClientTheLeader()) {
Log.info('[LOCAL_NOTIFICATION] Skipping notification because this client is not the leader');
return;
}

// We don't want to send a local notification if the user preference is daily or mute
if (notificationPreference === CONST.REPORT.NOTIFICATION_PREFERENCE.MUTE || notificationPreference === CONST.REPORT.NOTIFICATION_PREFERENCE.DAILY) {
Log.info(`[LOCAL_NOTIFICATION] No notification because user preference is to be notified: ${notificationPreference}`);
return;
}

// If this comment is from the current user we don't want to parrot whatever they wrote back to them.
if (isFromCurrentUser) {
Log.info('[LOCAL_NOTIFICATION] No notification because comment is from the currently logged in user');
return;
}

// If we are currently viewing this report do not show a notification.
if (reportID === lastViewedReportID && Visibility.isVisible()) {
Log.info('[LOCAL_NOTIFICATION] No notification because it was a comment for the current report');
return;
}
Comment on lines +1520 to +1524
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We didn't consider whether the window has focus, which led to a regression here


// If the comment came from Concierge let's not show a notification since we already show one for expensify.com
if (lodashGet(action, 'actorEmail') === CONST.EMAIL.CONCIERGE) {
return;
}

// 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);
}

Log.info('[LOCAL_NOTIFICATION] Creating notification');
LocalNotification.showCommentNotification({
reportAction: action,
onClick: () => {
// Navigate to this report onClick
Navigation.navigate(ROUTES.getReportRoute(reportID));
},
});
}

Onyx.connect({
key: ONYXKEYS.COLLECTION.REPORT_ACTIONS,
initWithStoredValues: false,
callback: (actions, key) => {
// reportID can be derived from the Onyx key
const reportID = parseInt(key.split('_')[1], 10);
if (!reportID) {
return;
}

_.each(actions, (action) => {
if (!action.timestamp) {
return;
}

// If we are past the deadline to notify for this comment don't do it
if (moment.utc(action.timestamp * 1000).isBefore(moment.utc().subtract(10, 'seconds'))) {
return;
}
marcaaron marked this conversation as resolved.
Show resolved Hide resolved

viewNewReportAction(reportID, action);
});
},
});

export {
fetchAllReports,
fetchActions,
Expand Down
2 changes: 2 additions & 0 deletions tests/actions/ReportTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ describe('actions/Report', () => {
});

it('should store a new report action in Onyx when reportComment event is handled via Pusher', () => {
global.fetch = TestHelper.getGlobalFetchMock();

const TEST_USER_ACCOUNT_ID = 1;
const TEST_USER_LOGIN = 'test@test.com';
const REPORT_ID = 1;
Expand Down