Skip to content

Commit

Permalink
Merge pull request #9328 from Expensify/marcaaron-localNotifications
Browse files Browse the repository at this point in the history
[No QA] Report_AddComment Refactor Part 1
  • Loading branch information
yuwenmemon authored Jun 7, 2022
2 parents a41ccf3 + a6910e7 commit 4a1b55d
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 87 deletions.
201 changes: 114 additions & 87 deletions src/libs/actions/Report.js
Original file line number Diff line number Diff line change
Expand Up @@ -548,42 +548,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 @@ -601,63 +579,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 @@ -1500,6 +1421,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);

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

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

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

0 comments on commit 4a1b55d

Please sign in to comment.