From 6e148438b358e99f642f547b922a7be77b73d32f Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Wed, 2 Sep 2020 10:54:56 -0600 Subject: [PATCH 01/31] Remove more get() from report action --- src/lib/Network.js | 9 +-- src/lib/actions/Report.js | 152 +++++++++++++------------------------- 2 files changed, 56 insertions(+), 105 deletions(-) diff --git a/src/lib/Network.js b/src/lib/Network.js index 382d7664b624..2ae3391fa72e 100644 --- a/src/lib/Network.js +++ b/src/lib/Network.js @@ -1,5 +1,4 @@ import _ from 'underscore'; -import lodashGet from 'lodash.get'; import NetInfo from '@react-native-community/netinfo'; import Ion from './Ion'; import CONFIG from '../CONFIG'; @@ -11,16 +10,16 @@ import Guid from './Guid'; import redirectToSignIn from './actions/SignInRedirect'; let authToken; -Ion.connect({key: IONKEYS.SESSION, callback: s => authToken = lodashGet(s, 'authToken', '')}); +Ion.connect({key: IONKEYS.SESSION, path: 'authToken', callback: val => authToken = val}); let isOffline; -Ion.connect({key: IONKEYS.NETWORK, callback: n => isOffline = lodashGet(n, 'isOffline', false)}); +Ion.connect({key: IONKEYS.NETWORK, path: 'isOffline', callback: val => isOffline = val}); let credentials; -Ion.connect({key: IONKEYS.CREDENTIALS, callback: c => credentials = c}); +Ion.connect({key: IONKEYS.CREDENTIALS, callback: val => credentials = val}); let currentUrl; -Ion.connect({key: IONKEYS.CURRENT_URL, callback: url => currentUrl = url}); +Ion.connect({key: IONKEYS.CURRENT_URL, callback: val => currentUrl = val}); /** * When authTokens expire they will automatically be refreshed. diff --git a/src/lib/actions/Report.js b/src/lib/actions/Report.js index 5830a2025f89..1d7c42f3ff67 100644 --- a/src/lib/actions/Report.js +++ b/src/lib/actions/Report.js @@ -11,6 +11,23 @@ import ExpensiMark from '../ExpensiMark'; import Notification from '../Notification'; import * as PersonalDetails from './PersonalDetails'; +let currentUserEmail; +Ion.connect({key: IONKEYS.SESSION, path: 'email', callback: val => currentUserEmail = val}); + +let currentUserAccountID; +Ion.connect({key: IONKEYS.SESSION, path: 'accountID', callback: val => currentUserAccountID = val}); + +let currentURL; +Ion.connect({key: IONKEYS.CURRENT_URL, callback: val => currentURL = val}); + +let personalDetails; + +// Use a regex pattern here for an exact match so it doesn't also match "my_personal_details" +Ion.connect({key: `^${IONKEYS.PERSONAL_DETAILS}$`, callback: val => personalDetails = val}); + +let myPersonalDetails; +Ion.connect({key: IONKEYS.MY_PERSONAL_DETAILS, callback: val => myPersonalDetails = val}); + /** * Checks the report to see if there are any unread history items * @@ -42,11 +59,9 @@ function hasUnreadHistoryItems(accountID, report) { * Returns a generated report title based on the participants * * @param {array} sharedReportList - * @param {object} personalDetails - * @param {string} currentUserEmail * @return {string} */ -function getChatReportName(sharedReportList, personalDetails, currentUserEmail) { +function getChatReportName(sharedReportList) { return _.chain(sharedReportList) .map(participant => participant.email) .filter(participant => participant !== currentUserEmail) @@ -64,19 +79,16 @@ function getChatReportName(sharedReportList, personalDetails, currentUserEmail) * @param {string} report.reportName * @param {object} report.reportNameValuePairs * @param {object} report.sharedReportList - * @param {string} accountID - * @param {object} [personalDetails] - * @param {string} [currentUserEmail] * @returns {object} */ -function getSimplifiedReportObject(report, accountID, personalDetails = {}, currentUserEmail = '') { +function getSimplifiedReportObject(report) { return { reportID: report.reportID, reportName: !_.isEmpty(personalDetails) && lodashGet(report, 'reportNameValuePairs.type') === 'chat' - ? getChatReportName(report.sharedReportList, personalDetails, currentUserEmail) + ? getChatReportName(report.sharedReportList) : report.reportName, reportNameValuePairs: report.reportNameValuePairs, - hasUnread: hasUnreadHistoryItems(accountID, report), + hasUnread: hasUnreadHistoryItems(currentUserAccountID, report), }; } @@ -88,19 +100,12 @@ function getSimplifiedReportObject(report, accountID, personalDetails = {}, curr * @return {Promise} */ function fetchChatReportsByIDs(chatList) { - let currentUserEmail; - let currentUserAccountID; let fetchedReports; - return Ion.get(IONKEYS.SESSION) - .then((session) => { - currentUserEmail = session.email; - currentUserAccountID = session.accountID; - return queueRequest('Get', { - returnValueList: 'reportStuff', - reportIDList: chatList.join(','), - shouldLoadOptionalKeys: true, - }); - }) + return queueRequest('Get', { + returnValueList: 'reportStuff', + reportIDList: chatList.join(','), + shouldLoadOptionalKeys: true, + }) .then(({reports}) => { fetchedReports = reports; @@ -118,16 +123,11 @@ function fetchChatReportsByIDs(chatList) { return PersonalDetails.getForEmails(emails.join(',')); }) - .then((personalDetails) => { + .then(() => { // Process the reports and store them in Ion const ionPromises = _.map(fetchedReports, (report) => { // Store only the absolute bare minimum of data in Ion because space is limited - const newReport = getSimplifiedReportObject( - report, - currentUserAccountID, - personalDetails, - currentUserEmail - ); + const newReport = getSimplifiedReportObject(report); // Merge the data into Ion. Don't use set() here or multiSet() because then that would // overwrite any existing data (like if they have unread messages) @@ -145,7 +145,6 @@ function fetchChatReportsByIDs(chatList) { * @param {object} reportAction */ function updateReportWithNewAction(reportID, reportAction) { - let currentUserEmail; Ion.get(`${IONKEYS.REPORT}_${reportID}`, 'reportID') .then((ionReportID) => { // This is necessary for local development because there will be pusher events from other engineers with @@ -186,19 +185,13 @@ function updateReportWithNewAction(reportID, reportAction) { // Put the report history back into Ion .then(reportHistory => Ion.set(`${IONKEYS.REPORT_HISTORY}_${reportID}`, reportHistory)) - // Check to see if we need to show a notification for this report - .then(() => Ion.get(IONKEYS.SESSION, 'email')) - .then((email) => { - currentUserEmail = email; - return Ion.get(IONKEYS.CURRENT_URL); - }) - .then((currentUrl) => { + .then(() => { // If this comment is from the current user we don't want to parrot whatever they wrote back to them. if (reportAction.actorEmail === currentUserEmail) { return; } - const currentReportID = Number(lodashGet(currentUrl.split('/'), [1], 0)); + const currentReportID = Number(lodashGet(currentURL.split('/'), [1], 0)); // If we are currently viewing this report do not show a notification. if (reportID === currentReportID) { @@ -217,17 +210,12 @@ function updateReportWithNewAction(reportID, reportAction) { /** * Initialize our pusher subscriptions to listen for new report comments - * - * @returns {Promise} */ function subscribeToReportCommentEvents() { - return Ion.get(IONKEYS.SESSION, 'accountID') - .then((accountID) => { - const pusherChannelName = `private-user-accountID-${accountID}`; - Pusher.subscribe(pusherChannelName, 'reportComment', (pushJSON) => { - updateReportWithNewAction(pushJSON.reportID, pushJSON.reportAction); - }); - }); + const pusherChannelName = `private-user-accountID-${currentUserAccountID}`; + Pusher.subscribe(pusherChannelName, 'reportComment', (pushJSON) => { + updateReportWithNewAction(pushJSON.reportID, pushJSON.reportAction); + }); } /** @@ -274,11 +262,10 @@ function fetchAll() { return _.isEmpty(report) ? null : _.values(report)[0]; }))) .then(() => Ion.set(IONKEYS.FIRST_REPORT_ID, _.first(_.pluck(fetchedReports, 'reportID')) || 0)) - .then(() => Ion.get(IONKEYS.SESSION, 'accountID')) - .then((accountID) => { + .then(() => { const ionPromises = _.map(fetchedReports, (report) => { // Store only the absolute bare minimum of data in Ion because space is limited - const newReport = getSimplifiedReportObject(report, accountID); + const newReport = getSimplifiedReportObject(report); // Merge the data into Ion. Don't use set() here or multiSet() because then that would // overwrite any existing data (like if they have unread messages) @@ -315,24 +302,13 @@ function fetchHistory(reportID) { * @returns {Promise} */ function fetchChatReport(participants) { - let currentUserEmail; - let currentUserAccountID; - let personalDetails; let reportID; // Get the current users accountID and set it aside in a local variable // which is used for checking if there are unread comments - return Ion.multiGet([IONKEYS.SESSION, IONKEYS.PERSONAL_DETAILS]) - .then((data) => { - currentUserEmail = data.session.email; - currentUserAccountID = data.session.accountID; - personalDetails = data.personal_details; - }) - - // Make a request to get the reportID for this list of participants - .then(() => queueRequest('CreateChatReport', { - emailList: participants.join(','), - })) + return queueRequest('CreateChatReport', { + emailList: participants.join(','), + }) // Set aside the reportID in a local variable so it can be accessed in the rest of the chain .then(data => reportID = data.reportID) @@ -349,12 +325,7 @@ function fetchChatReport(participants) { const report = data.reports[reportID]; // Store only the absolute bare minimum of data in Ion because space is limited - const newReport = getSimplifiedReportObject( - report, - currentUserAccountID, - personalDetails, - currentUserEmail - ); + const newReport = getSimplifiedReportObject(report); // Merge the data into Ion. Don't use set() here or multiSet() because then that would // overwrite any existing data (like if they have unread messages) @@ -371,9 +342,6 @@ function fetchChatReport(participants) { * @param {string} reportID */ function fetchReportByIDIfNotExists(reportID) { - let currentUserAccountID; - let currentUserEmail; - let personalDetails; const reportIDKey = `${IONKEYS.REPORT}_${reportID}`; Ion.get(reportIDKey) @@ -382,31 +350,19 @@ function fetchReportByIDIfNotExists(reportID) { return; } - Ion.multiGet([IONKEYS.SESSION, IONKEYS.PERSONAL_DETAILS]) - .then((data) => { - currentUserEmail = data.session.email; - currentUserAccountID = data.session.accountID; - personalDetails = data.personal_details; - - // We don't have this report so let's fetch it - return queueRequest('Get', { - returnValueList: 'reportStuff', - reportIDList: reportID, - shouldLoadOptionalKeys: true, - }); - }) + // We don't have this report so let's fetch it + queueRequest('Get', { + returnValueList: 'reportStuff', + reportIDList: reportID, + shouldLoadOptionalKeys: true, + }) .then(({reports}) => { if (reports === undefined) { return; } // Store only the absolute bare minimum of data in Ion because space is limited - const newReport = getSimplifiedReportObject( - reports[reportID], - currentUserAccountID, - personalDetails, - currentUserEmail - ); + const newReport = getSimplifiedReportObject(reports[reportID]); Ion.merge(reportIDKey, newReport); }); }); @@ -426,12 +382,8 @@ function addHistoryItem(reportID, reportComment) { const parser = new ExpensiMark(); const htmlComment = parser.replace(reportComment); - return Ion.multiGet([historyKey, IONKEYS.SESSION, IONKEYS.PERSONAL_DETAILS]) - .then((values) => { - const reportHistory = values[historyKey]; - const email = values[IONKEYS.SESSION].email || ''; - const personalDetails = lodashGet(values, [IONKEYS.PERSONAL_DETAILS, email], {}); - + return Ion.get(historyKey) + .then((reportHistory) => { // The new sequence number will be one higher than the highest let highestSequenceNumber = _.chain(reportHistory) .pluck('sequenceNumber') @@ -444,17 +396,17 @@ function addHistoryItem(reportID, reportComment) { ...reportHistory, [newSequenceNumber]: { actionName: 'ADDCOMMENT', - actorEmail: email, + actorEmail: currentUserEmail, person: [ { style: 'strong', - text: personalDetails.displayName || email, + text: myPersonalDetails.displayName || currentUserEmail, type: 'TEXT' } ], automatic: false, sequenceNumber: ++highestSequenceNumber, - avatar: personalDetails.avatarURL, + avatar: myPersonalDetails.avatarURL, timestamp: moment().unix(), message: [ { From b7d4b65456f1c871cdeaf2131b08357f6000408c Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Wed, 2 Sep 2020 11:20:04 -0600 Subject: [PATCH 02/31] Remove some more instances of Ion.get() --- src/lib/actions/Report.js | 75 ++++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 37 deletions(-) diff --git a/src/lib/actions/Report.js b/src/lib/actions/Report.js index 1d7c42f3ff67..0cc481db25ac 100644 --- a/src/lib/actions/Report.js +++ b/src/lib/actions/Report.js @@ -28,6 +28,9 @@ Ion.connect({key: `^${IONKEYS.PERSONAL_DETAILS}$`, callback: val => personalDeta let myPersonalDetails; Ion.connect({key: IONKEYS.MY_PERSONAL_DETAILS, callback: val => myPersonalDetails = val}); +const currentReports = {}; +Ion.connect({key: `${IONKEYS.REPORT}_[0-9]+$`, callback: (val, key) => currentReports[key] = val}); + /** * Checks the report to see if there are any unread history items * @@ -145,26 +148,28 @@ function fetchChatReportsByIDs(chatList) { * @param {object} reportAction */ function updateReportWithNewAction(reportID, reportAction) { - Ion.get(`${IONKEYS.REPORT}_${reportID}`, 'reportID') - .then((ionReportID) => { - // This is necessary for local development because there will be pusher events from other engineers with - // different reportIDs. This means that while in development it's not possible to make new chats appear - // by creating chats then leaving comments in other windows. - if (!CONFIG.IS_IN_PRODUCTION && !ionReportID) { - throw new Error('report does not exist in the store, so ignoring new comments'); - } + let promise; - // When handling a realtime update for a chat that does not yet exist in our store we - // need to fetch it so that we can properly navigate to it. This enables us populate - // newly created chats in the LHN without requiring a full refresh of the app. - if (!ionReportID) { - return fetchChatReportsByIDs([reportID]) - .then(() => Ion.get(`${IONKEYS.REPORT_HISTORY}_${reportID}`)); - } + // This is necessary for local development because there will be pusher events from other engineers with + // different reportIDs. This means that while in development it's not possible to make new chats appear + // by creating chats then leaving comments in other windows. + if (!CONFIG.IS_IN_PRODUCTION && !currentReports[`${IONKEYS.REPORT}_${reportID}`]) { + throw new Error('report does not exist in the store, so ignoring new comments'); + } - // Get the report history and return that to the next chain - return Ion.get(`${IONKEYS.REPORT_HISTORY}_${reportID}`); - }) + // When handling a realtime update for a chat that does not yet exist in our store we + // need to fetch it so that we can properly navigate to it. This enables us populate + // newly created chats in the LHN without requiring a full refresh of the app. + if (!currentReports[`${IONKEYS.REPORT}_${reportID}`]) { + promise = fetchChatReportsByIDs([reportID]) + .then(() => Ion.get(`${IONKEYS.REPORT_HISTORY}_${reportID}`)); + } else { + // Get the report history and return that to the next chain + promise = Ion.get(`${IONKEYS.REPORT_HISTORY}_${reportID}`); + } + + // Get the report history and return that to the next chain + promise // Look to see if the report action from pusher already exists or not (it would exist if it's a comment just // written by the user). If the action doesn't exist, then update the unread flag on the report so the user @@ -342,29 +347,25 @@ function fetchChatReport(participants) { * @param {string} reportID */ function fetchReportByIDIfNotExists(reportID) { - const reportIDKey = `${IONKEYS.REPORT}_${reportID}`; + const report = currentReports[`${IONKEYS.REPORT}_${reportID}`]; + if (lodashGet(report, 'reportName', false)) { + return; + } - Ion.get(reportIDKey) - .then((report) => { - if (lodashGet(report, 'reportName', false)) { + // We don't have this report so let's fetch it + queueRequest('Get', { + returnValueList: 'reportStuff', + reportIDList: reportID, + shouldLoadOptionalKeys: true, + }) + .then(({reports}) => { + if (reports === undefined) { return; } - // We don't have this report so let's fetch it - queueRequest('Get', { - returnValueList: 'reportStuff', - reportIDList: reportID, - shouldLoadOptionalKeys: true, - }) - .then(({reports}) => { - if (reports === undefined) { - return; - } - - // Store only the absolute bare minimum of data in Ion because space is limited - const newReport = getSimplifiedReportObject(reports[reportID]); - Ion.merge(reportIDKey, newReport); - }); + // Store only the absolute bare minimum of data in Ion because space is limited + const newReport = getSimplifiedReportObject(reports[reportID]); + Ion.merge(`${IONKEYS.REPORT}_${reportID}`, newReport); }); } From d7634557aa58eed964e7a13f30afd54899db6b80 Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Wed, 2 Sep 2020 11:28:37 -0600 Subject: [PATCH 03/31] Remove final instances of Ion.get() --- src/lib/actions/Report.js | 85 ++++++++++++++++++++------------------- 1 file changed, 44 insertions(+), 41 deletions(-) diff --git a/src/lib/actions/Report.js b/src/lib/actions/Report.js index 0cc481db25ac..5483b93fda25 100644 --- a/src/lib/actions/Report.js +++ b/src/lib/actions/Report.js @@ -31,6 +31,9 @@ Ion.connect({key: IONKEYS.MY_PERSONAL_DETAILS, callback: val => myPersonalDetail const currentReports = {}; Ion.connect({key: `${IONKEYS.REPORT}_[0-9]+$`, callback: (val, key) => currentReports[key] = val}); +const currentReportHistories = {}; +Ion.connect({key: `${IONKEYS.REPORT_HISTORY}_[0-9]+$`, callback: (val, key) => currentReportHistories[key] = val}); + /** * Checks the report to see if there are any unread history items * @@ -162,10 +165,12 @@ function updateReportWithNewAction(reportID, reportAction) { // newly created chats in the LHN without requiring a full refresh of the app. if (!currentReports[`${IONKEYS.REPORT}_${reportID}`]) { promise = fetchChatReportsByIDs([reportID]) - .then(() => Ion.get(`${IONKEYS.REPORT_HISTORY}_${reportID}`)); + .then(() => currentReportHistories[`${IONKEYS.REPORT_HISTORY}_${reportID}`]); } else { // Get the report history and return that to the next chain - promise = Ion.get(`${IONKEYS.REPORT_HISTORY}_${reportID}`); + promise = new Promise((resolve) => { + resolve(currentReportHistories[`${IONKEYS.REPORT_HISTORY}_${reportID}`]); + }); } // Get the report history and return that to the next chain @@ -382,47 +387,45 @@ function addHistoryItem(reportID, reportComment) { // Convert the comment from MD into HTML because that's how it is stored in the database const parser = new ExpensiMark(); const htmlComment = parser.replace(reportComment); + const reportHistory = currentReportHistories[historyKey]; - return Ion.get(historyKey) - .then((reportHistory) => { - // The new sequence number will be one higher than the highest - let highestSequenceNumber = _.chain(reportHistory) - .pluck('sequenceNumber') - .max() - .value() || 0; - const newSequenceNumber = highestSequenceNumber + 1; - - // Optimistically add the new comment to the store before waiting to save it to the server - return Ion.set(historyKey, { - ...reportHistory, - [newSequenceNumber]: { - actionName: 'ADDCOMMENT', - actorEmail: currentUserEmail, - person: [ - { - style: 'strong', - text: myPersonalDetails.displayName || currentUserEmail, - type: 'TEXT' - } - ], - automatic: false, - sequenceNumber: ++highestSequenceNumber, - avatar: myPersonalDetails.avatarURL, - timestamp: moment().unix(), - message: [ - { - type: 'COMMENT', - html: htmlComment, - - // Remove HTML from text when applying optimistic offline comment - text: htmlComment.replace(/<[^>]*>?/gm, ''), - } - ], - isFirstItem: false, - isAttachmentPlaceHolder: false, + // The new sequence number will be one higher than the highest + let highestSequenceNumber = _.chain(reportHistory) + .pluck('sequenceNumber') + .max() + .value() || 0; + const newSequenceNumber = highestSequenceNumber + 1; + + // Optimistically add the new comment to the store before waiting to save it to the server + return Ion.set(historyKey, { + ...reportHistory, + [newSequenceNumber]: { + actionName: 'ADDCOMMENT', + actorEmail: currentUserEmail, + person: [ + { + style: 'strong', + text: myPersonalDetails.displayName || currentUserEmail, + type: 'TEXT' } - }); - }) + ], + automatic: false, + sequenceNumber: ++highestSequenceNumber, + avatar: myPersonalDetails.avatarURL, + timestamp: moment().unix(), + message: [ + { + type: 'COMMENT', + html: htmlComment, + + // Remove HTML from text when applying optimistic offline comment + text: htmlComment.replace(/<[^>]*>?/gm, ''), + } + ], + isFirstItem: false, + isAttachmentPlaceHolder: false, + } + }) .then(() => queueRequest('Report_AddComment', { reportID, reportComment: htmlComment, From 97e4a8dbe18b3baa6e5b272d1edd3b71bf30d1b2 Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Wed, 2 Sep 2020 11:53:58 -0600 Subject: [PATCH 04/31] Switch this to merge --- src/lib/actions/Report.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/lib/actions/Report.js b/src/lib/actions/Report.js index 5483b93fda25..0b2abc4ce829 100644 --- a/src/lib/actions/Report.js +++ b/src/lib/actions/Report.js @@ -397,8 +397,7 @@ function addHistoryItem(reportID, reportComment) { const newSequenceNumber = highestSequenceNumber + 1; // Optimistically add the new comment to the store before waiting to save it to the server - return Ion.set(historyKey, { - ...reportHistory, + return Ion.merge(historyKey, { [newSequenceNumber]: { actionName: 'ADDCOMMENT', actorEmail: currentUserEmail, From e180e6b33e420da90168df4a7b35cb4037cebfeb Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Wed, 2 Sep 2020 12:11:08 -0600 Subject: [PATCH 05/31] Connect to both keys at once --- src/lib/actions/Report.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/lib/actions/Report.js b/src/lib/actions/Report.js index 0b2abc4ce829..dbb0995af685 100644 --- a/src/lib/actions/Report.js +++ b/src/lib/actions/Report.js @@ -12,10 +12,11 @@ import Notification from '../Notification'; import * as PersonalDetails from './PersonalDetails'; let currentUserEmail; -Ion.connect({key: IONKEYS.SESSION, path: 'email', callback: val => currentUserEmail = val}); - let currentUserAccountID; -Ion.connect({key: IONKEYS.SESSION, path: 'accountID', callback: val => currentUserAccountID = val}); +Ion.connect({key: IONKEYS.SESSION, callback: (val) => { + currentUserEmail = val.email; + currentUserAccountID = val.accountID; +}}); let currentURL; Ion.connect({key: IONKEYS.CURRENT_URL, callback: val => currentURL = val}); From 06bfe4866b2423e0eb8b4be1b942252609ab7c0c Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Wed, 2 Sep 2020 16:51:35 -0600 Subject: [PATCH 06/31] Correcting file after merge conflicts --- src/lib/actions/Report.js | 36 +++++++++++++----------------------- 1 file changed, 13 insertions(+), 23 deletions(-) diff --git a/src/lib/actions/Report.js b/src/lib/actions/Report.js index e048c8aea395..cdc474f1d515 100644 --- a/src/lib/actions/Report.js +++ b/src/lib/actions/Report.js @@ -70,10 +70,9 @@ function hasUnreadHistoryItems(accountID, report) { * @param {number} report.reportID * @param {string} report.reportName * @param {object} report.reportNameValuePairs - * @param {string} accountID * @returns {object} */ -function getSimplifiedReportObject(report, accountID) { +function getSimplifiedReportObject(report) { return { reportID: report.reportID, reportName: report.reportName, @@ -86,11 +85,9 @@ function getSimplifiedReportObject(report, accountID) { * Returns a generated report title based on the participants * * @param {array} sharedReportList - * @param {object} personalDetails - * @param {string} currentUserEmail * @return {string} */ -function getChatReportName(sharedReportList, personalDetails, currentUserEmail) { +function getChatReportName(sharedReportList) { return _.chain(sharedReportList) .map(participant => participant.email) .filter(participant => participant !== currentUserEmail) @@ -133,14 +130,10 @@ function fetchChatReportsByIDs(chatList) { .then(() => { // Process the reports and store them in Ion const ionPromises = _.map(fetchedReports, (report) => { - const newReport = getSimplifiedReportObject(report, currentUserAccountID); + const newReport = getSimplifiedReportObject(report); if (lodashGet(report, 'reportNameValuePairs.type') === 'chat') { - newReport.reportName = getChatReportName( - report.sharedReportList, - personalDetails, - currentUserEmail - ); + newReport.reportName = getChatReportName(report.sharedReportList); } // Merge the data into Ion. Don't use set() here or multiSet() because then that would @@ -230,17 +223,14 @@ function updateReportWithNewAction(reportID, reportAction) { * Initialize our pusher subscriptions to listen for new report comments */ function subscribeToReportCommentEvents() { - return Ion.get(IONKEYS.SESSION, 'accountID') - .then((accountID) => { - const pusherChannelName = `private-user-accountID-${accountID}`; - if (Pusher.isSubscribed(pusherChannelName) || Pusher.isAlreadySubscribing(pusherChannelName)) { - return; - } + const pusherChannelName = `private-user-accountID-${currentUserAccountID}`; + if (Pusher.isSubscribed(pusherChannelName) || Pusher.isAlreadySubscribing(pusherChannelName)) { + return; + } - Pusher.subscribe(pusherChannelName, 'reportComment', (pushJSON) => { - updateReportWithNewAction(pushJSON.reportID, pushJSON.reportAction); - }); - }); + Pusher.subscribe(pusherChannelName, 'reportComment', (pushJSON) => { + updateReportWithNewAction(pushJSON.reportID, pushJSON.reportAction); + }); } /** @@ -350,8 +340,8 @@ function fetchChatReport(participants) { const report = data.reports[reportID]; // Store only the absolute bare minimum of data in Ion because space is limited - const newReport = getSimplifiedReportObject(report, currentUserAccountID); - newReport.reportName = getChatReportName(report.sharedReportList, personalDetails, currentUserEmail); + const newReport = getSimplifiedReportObject(report); + newReport.reportName = getChatReportName(report.sharedReportList); // Merge the data into Ion. Don't use set() here or multiSet() because then that would // overwrite any existing data (like if they have unread messages) From 9f3f432d75865a4d61bb85173f184b08269d2377 Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Thu, 3 Sep 2020 10:33:05 -0600 Subject: [PATCH 07/31] Only keep track of the reportIDs that are known --- src/lib/actions/Report.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/lib/actions/Report.js b/src/lib/actions/Report.js index cdc474f1d515..a57ccb3c42e8 100644 --- a/src/lib/actions/Report.js +++ b/src/lib/actions/Report.js @@ -29,11 +29,17 @@ Ion.connect({key: `^${IONKEYS.PERSONAL_DETAILS}$`, callback: val => personalDeta let myPersonalDetails; Ion.connect({key: IONKEYS.MY_PERSONAL_DETAILS, callback: val => myPersonalDetails = val}); -const currentReports = {}; -Ion.connect({key: `${IONKEYS.REPORT}_[0-9]+$`, callback: (val, key) => currentReports[key] = val}); +const currentReportIDs = {}; +Ion.connect({key: `${IONKEYS.REPORT}_[0-9]+$`, callback: (val, key) => { + // Keep track of all the reportIDs that we know about. They are kept on the keys of an object so it is super + // fast to look them up + currentReportIDs[key.replace(`${IONKEYS.REPORT}_`, '')] = ''; +}}); const currentReportHistories = {}; -Ion.connect({key: `${IONKEYS.REPORT_HISTORY}_[0-9]+$`, callback: (val, key) => currentReportHistories[key] = val}); +Ion.connect({key: `${IONKEYS.REPORT_HISTORY}_[0-9]+$`, callback: (val, key) => { + currentReportHistories[key] = val; +}}); /** * Checks the report to see if there are any unread history items @@ -157,14 +163,14 @@ function updateReportWithNewAction(reportID, reportAction) { // This is necessary for local development because there will be pusher events from other engineers with // different reportIDs. This means that while in development it's not possible to make new chats appear // by creating chats then leaving comments in other windows. - if (!CONFIG.IS_IN_PRODUCTION && !currentReports[`${IONKEYS.REPORT}_${reportID}`]) { + if (!CONFIG.IS_IN_PRODUCTION && !currentReportIDs[reportID]) { throw new Error('report does not exist in the store, so ignoring new comments'); } // When handling a realtime update for a chat that does not yet exist in our store we // need to fetch it so that we can properly navigate to it. This enables us populate // newly created chats in the LHN without requiring a full refresh of the app. - if (!currentReports[`${IONKEYS.REPORT}_${reportID}`]) { + if (!currentReportIDs[reportID]) { promise = fetchChatReportsByIDs([reportID]) .then(() => currentReportHistories[`${IONKEYS.REPORT_HISTORY}_${reportID}`]); } else { From de54b8669433fdfa1173380b8da5b7ac894542ab Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Thu, 3 Sep 2020 10:55:37 -0600 Subject: [PATCH 08/31] Only keep track of reportIDs --- src/lib/actions/Report.js | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/lib/actions/Report.js b/src/lib/actions/Report.js index a57ccb3c42e8..b44b0df3302c 100644 --- a/src/lib/actions/Report.js +++ b/src/lib/actions/Report.js @@ -11,6 +11,8 @@ import ExpensiMark from '../ExpensiMark'; import Notification from '../Notification'; import * as PersonalDetails from './PersonalDetails'; +// Disabling these rules makes the following code more compact and easier to read for these instances +/* eslint-disable object-curly-newline,object-property-newline */ let currentUserEmail; let currentUserAccountID; Ion.connect({key: IONKEYS.SESSION, callback: (val) => { @@ -21,25 +23,25 @@ Ion.connect({key: IONKEYS.SESSION, callback: (val) => { let currentURL; Ion.connect({key: IONKEYS.CURRENT_URL, callback: val => currentURL = val}); -let personalDetails; - // Use a regex pattern here for an exact match so it doesn't also match "my_personal_details" +let personalDetails; Ion.connect({key: `^${IONKEYS.PERSONAL_DETAILS}$`, callback: val => personalDetails = val}); let myPersonalDetails; Ion.connect({key: IONKEYS.MY_PERSONAL_DETAILS, callback: val => myPersonalDetails = val}); -const currentReportIDs = {}; +const currentReportIDs = []; Ion.connect({key: `${IONKEYS.REPORT}_[0-9]+$`, callback: (val, key) => { - // Keep track of all the reportIDs that we know about. They are kept on the keys of an object so it is super + // Keep track of all the reportIDs that are known. They are kept on the keys of an object so it is super // fast to look them up - currentReportIDs[key.replace(`${IONKEYS.REPORT}_`, '')] = ''; + currentReportIDs.push(parseInt(key.replace(`${IONKEYS.REPORT}_`, ''), 10)); }}); const currentReportHistories = {}; Ion.connect({key: `${IONKEYS.REPORT_HISTORY}_[0-9]+$`, callback: (val, key) => { currentReportHistories[key] = val; }}); +/* eslint-enable object-curly-newline,object-property-newline */ /** * Checks the report to see if there are any unread history items @@ -158,19 +160,20 @@ function fetchChatReportsByIDs(chatList) { * @param {object} reportAction */ function updateReportWithNewAction(reportID, reportAction) { + const reportExistsInIon = _.contains(currentReportIDs, reportID); let promise; // This is necessary for local development because there will be pusher events from other engineers with // different reportIDs. This means that while in development it's not possible to make new chats appear // by creating chats then leaving comments in other windows. - if (!CONFIG.IS_IN_PRODUCTION && !currentReportIDs[reportID]) { + if (!CONFIG.IS_IN_PRODUCTION && !reportExistsInIon) { throw new Error('report does not exist in the store, so ignoring new comments'); } // When handling a realtime update for a chat that does not yet exist in our store we // need to fetch it so that we can properly navigate to it. This enables us populate // newly created chats in the LHN without requiring a full refresh of the app. - if (!currentReportIDs[reportID]) { + if (!reportExistsInIon) { promise = fetchChatReportsByIDs([reportID]) .then(() => currentReportHistories[`${IONKEYS.REPORT_HISTORY}_${reportID}`]); } else { @@ -190,18 +193,13 @@ function updateReportWithNewAction(reportID, reportAction) { if (reportHistory && !reportHistory[reportAction.sequenceNumber]) { Ion.merge(`${IONKEYS.REPORT}_${reportID}`, {hasUnread: true}); } - return reportHistory || {}; }) - // Put the report action from pusher into the history, it's OK to overwrite it if it already exists - .then(reportHistory => ({ - ...reportHistory, + // Merge the new action into Ion + .then(() => Ion.merge(`${IONKEYS.REPORT_HISTORY}_${reportID}`, { [reportAction.sequenceNumber]: reportAction, })) - // Put the report history back into Ion - .then(reportHistory => Ion.set(`${IONKEYS.REPORT_HISTORY}_${reportID}`, reportHistory)) - .then(() => { // If this comment is from the current user we don't want to parrot whatever they wrote back to them. if (reportAction.actorEmail === currentUserEmail) { From ceed33fbf08aed3dd3bbb72156d5edd9eb440082 Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Thu, 3 Sep 2020 12:23:09 -0600 Subject: [PATCH 09/31] Properly handle missing report names --- src/page/home/sidebar/SidebarLink.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/page/home/sidebar/SidebarLink.js b/src/page/home/sidebar/SidebarLink.js index bcb477b2665c..02aeacb2d40e 100644 --- a/src/page/home/sidebar/SidebarLink.js +++ b/src/page/home/sidebar/SidebarLink.js @@ -14,7 +14,7 @@ const propTypes = { reportID: PropTypes.number.isRequired, // The name of the report to use as the text for this link - reportName: PropTypes.string.isRequired, + reportName: PropTypes.string, // These are from withRouter // eslint-disable-next-line react/forbid-prop-types @@ -31,9 +31,14 @@ const propTypes = { const defaultProps = { isUnread: false, + reportName: '', }; const SidebarLink = (props) => { + if (!props.reportName) { + return null; + } + const paramsReportID = parseInt(props.match.params.reportID, 10); const isReportActive = paramsReportID === props.reportID; const linkWrapperActiveStyle = isReportActive && styles.sidebarLinkWrapperActive; From 01d273dbca1427cb063b51a5b2476b46f29dabad Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Thu, 3 Sep 2020 12:23:25 -0600 Subject: [PATCH 10/31] Remove unnecessary prop for reportID --- src/page/home/report/ReportHistoryCompose.js | 5 +---- src/page/home/report/ReportView.js | 3 +-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/page/home/report/ReportHistoryCompose.js b/src/page/home/report/ReportHistoryCompose.js index 5cc79358796b..b2706b2e938c 100644 --- a/src/page/home/report/ReportHistoryCompose.js +++ b/src/page/home/report/ReportHistoryCompose.js @@ -8,9 +8,6 @@ import sendIcon from '../../../../assets/images/icon-send.png'; const propTypes = { // A method to call when the form is submitted onSubmit: PropTypes.func.isRequired, - - // The ID of the report actions will be created for - reportID: PropTypes.number.isRequired, }; class ReportHistoryCompose extends React.Component { @@ -69,7 +66,7 @@ class ReportHistoryCompose extends React.Component { return; } - this.props.onSubmit(this.props.reportID, trimmedComment); + this.props.onSubmit(trimmedComment); this.setState({ comment: '', }); diff --git a/src/page/home/report/ReportView.js b/src/page/home/report/ReportView.js index 6c9aa2bd5728..cac7ce4d143c 100644 --- a/src/page/home/report/ReportView.js +++ b/src/page/home/report/ReportView.js @@ -28,8 +28,7 @@ class ReportView extends React.PureComponent { {shouldShowComposeForm && ( addHistoryItem(this.props.reportID, text)} /> )} From 9acad6ae68bc1a147821c5c276ae1133a234d41f Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Thu, 3 Sep 2020 12:23:41 -0600 Subject: [PATCH 11/31] Handle unknown reports better --- src/lib/actions/Report.js | 47 ++++++++++++++------------------------- src/page/home/MainView.js | 2 +- 2 files changed, 18 insertions(+), 31 deletions(-) diff --git a/src/lib/actions/Report.js b/src/lib/actions/Report.js index b44b0df3302c..6fe09aed424e 100644 --- a/src/lib/actions/Report.js +++ b/src/lib/actions/Report.js @@ -16,8 +16,10 @@ import * as PersonalDetails from './PersonalDetails'; let currentUserEmail; let currentUserAccountID; Ion.connect({key: IONKEYS.SESSION, callback: (val) => { - currentUserEmail = val.email; - currentUserAccountID = val.accountID; + if (val) { + currentUserEmail = val.email; + currentUserAccountID = val.accountID; + } }}); let currentURL; @@ -30,11 +32,12 @@ Ion.connect({key: `^${IONKEYS.PERSONAL_DETAILS}$`, callback: val => personalDeta let myPersonalDetails; Ion.connect({key: IONKEYS.MY_PERSONAL_DETAILS, callback: val => myPersonalDetails = val}); -const currentReportIDs = []; -Ion.connect({key: `${IONKEYS.REPORT}_[0-9]+$`, callback: (val, key) => { - // Keep track of all the reportIDs that are known. They are kept on the keys of an object so it is super - // fast to look them up - currentReportIDs.push(parseInt(key.replace(`${IONKEYS.REPORT}_`, ''), 10)); +// Listen for all reports added to Ion and if there is one that doesn't have a name, then +// fetch that report from the server so that it has all updated information about it +Ion.connect({key: `${IONKEYS.REPORT}_[0-9]+$`, callback: val => { + if (val && val.reportName === undefined) { + fetchChatReportsByIDs([val.reportID]); + } }}); const currentReportHistories = {}; @@ -160,31 +163,15 @@ function fetchChatReportsByIDs(chatList) { * @param {object} reportAction */ function updateReportWithNewAction(reportID, reportAction) { - const reportExistsInIon = _.contains(currentReportIDs, reportID); - let promise; - - // This is necessary for local development because there will be pusher events from other engineers with - // different reportIDs. This means that while in development it's not possible to make new chats appear - // by creating chats then leaving comments in other windows. - if (!CONFIG.IS_IN_PRODUCTION && !reportExistsInIon) { - throw new Error('report does not exist in the store, so ignoring new comments'); - } - - // When handling a realtime update for a chat that does not yet exist in our store we - // need to fetch it so that we can properly navigate to it. This enables us populate - // newly created chats in the LHN without requiring a full refresh of the app. - if (!reportExistsInIon) { - promise = fetchChatReportsByIDs([reportID]) - .then(() => currentReportHistories[`${IONKEYS.REPORT_HISTORY}_${reportID}`]); - } else { - // Get the report history and return that to the next chain - promise = new Promise((resolve) => { - resolve(currentReportHistories[`${IONKEYS.REPORT_HISTORY}_${reportID}`]); - }); - } + // Always merge the reportID into Ion + // If the report doesn't exist in Ion yet, then all the rest of the data will be filled out + // from the code at the top of this file + Ion.merge(`${IONKEYS.REPORT}_${reportID}`, {reportID}); // Get the report history and return that to the next chain - promise + new Promise((resolve) => { + resolve(currentReportHistories[`${IONKEYS.REPORT_HISTORY}_${reportID}`]); + }) // Look to see if the report action from pusher already exists or not (it would exist if it's a comment just // written by the user). If the action doesn't exist, then update the unread flag on the report so the user diff --git a/src/page/home/MainView.js b/src/page/home/MainView.js index 80b9eb97bfc2..f22553a8125c 100644 --- a/src/page/home/MainView.js +++ b/src/page/home/MainView.js @@ -55,7 +55,7 @@ class MainView extends React.Component { return ( <> - {_.map(this.props.reports, report => ( + {_.map(this.props.reports, report => report.reportID && ( Date: Thu, 3 Sep 2020 13:53:54 -0600 Subject: [PATCH 12/31] Rename some of the ion keys so they follow conventions --- src/IONKEYS.js | 7 +++---- src/lib/ActiveClientManager.js | 8 ++++---- src/lib/actions/Report.js | 10 +++++----- src/page/home/report/ReportHistoryView.js | 2 +- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/IONKEYS.js b/src/IONKEYS.js index 6f8204e68152..2a345c9b235b 100644 --- a/src/IONKEYS.js +++ b/src/IONKEYS.js @@ -2,16 +2,15 @@ * This is a file containing constants for all the top level keys in our store */ export default { - ACTIVE_CLIENT_IDS: 'activeClientIDs', + ACTIVE_CLIENTS: 'active_clients', APP_REDIRECT_TO: 'app_redirectTo', CURRENT_URL: 'current_url', CREDENTIALS: 'credentials', + FIRST_REPORT_ID: 'first_report_id', MY_PERSONAL_DETAILS: 'my_personal_details', NETWORK: 'network', PERSONAL_DETAILS: 'personal_details', REPORT: 'report', - REPORT_ACTION: 'reportAction', - REPORT_HISTORY: 'report_history', - FIRST_REPORT_ID: 'first_report_id', + REPORT_ACTIONS: 'report_actions', SESSION: 'session', }; diff --git a/src/lib/ActiveClientManager.js b/src/lib/ActiveClientManager.js index 2ffde8ce8be6..8d68c313237e 100644 --- a/src/lib/ActiveClientManager.js +++ b/src/lib/ActiveClientManager.js @@ -10,7 +10,7 @@ const clientID = Guid(); * * @returns {Promise} */ -const init = () => Ion.merge(IONKEYS.ACTIVE_CLIENT_IDS, {clientID}); +const init = () => Ion.merge(IONKEYS.ACTIVE_CLIENTS, {clientID}); /** * Remove this client ID from the array of active client IDs when this client is exited @@ -18,9 +18,9 @@ const init = () => Ion.merge(IONKEYS.ACTIVE_CLIENT_IDS, {clientID}); * @returns {Promise} */ function removeClient() { - return Ion.get(IONKEYS.ACTIVE_CLIENT_IDS) + return Ion.get(IONKEYS.ACTIVE_CLIENTS) .then(activeClientIDs => _.omit(activeClientIDs, clientID)) - .then(newActiveClientIDs => Ion.set(IONKEYS.ACTIVE_CLIENT_IDS, newActiveClientIDs)); + .then(newActiveClientIDs => Ion.set(IONKEYS.ACTIVE_CLIENTS, newActiveClientIDs)); } /** @@ -29,7 +29,7 @@ function removeClient() { * @returns {Promise} */ function isClientTheLeader() { - return Ion.get(IONKEYS.ACTIVE_CLIENT_IDS) + return Ion.get(IONKEYS.ACTIVE_CLIENTS) .then(activeClientIDs => _.first(activeClientIDs) === clientID); } diff --git a/src/lib/actions/Report.js b/src/lib/actions/Report.js index 6fe09aed424e..da89f8878f3d 100644 --- a/src/lib/actions/Report.js +++ b/src/lib/actions/Report.js @@ -41,7 +41,7 @@ Ion.connect({key: `${IONKEYS.REPORT}_[0-9]+$`, callback: val => { }}); const currentReportHistories = {}; -Ion.connect({key: `${IONKEYS.REPORT_HISTORY}_[0-9]+$`, callback: (val, key) => { +Ion.connect({key: `${IONKEYS.REPORT_ACTIONS}_[0-9]+$`, callback: (val, key) => { currentReportHistories[key] = val; }}); /* eslint-enable object-curly-newline,object-property-newline */ @@ -170,7 +170,7 @@ function updateReportWithNewAction(reportID, reportAction) { // Get the report history and return that to the next chain new Promise((resolve) => { - resolve(currentReportHistories[`${IONKEYS.REPORT_HISTORY}_${reportID}`]); + resolve(currentReportHistories[`${IONKEYS.REPORT_ACTIONS}_${reportID}`]); }) // Look to see if the report action from pusher already exists or not (it would exist if it's a comment just @@ -183,7 +183,7 @@ function updateReportWithNewAction(reportID, reportAction) { }) // Merge the new action into Ion - .then(() => Ion.merge(`${IONKEYS.REPORT_HISTORY}_${reportID}`, { + .then(() => Ion.merge(`${IONKEYS.REPORT_ACTIONS}_${reportID}`, { [reportAction.sequenceNumber]: reportAction, })) @@ -296,7 +296,7 @@ function fetchHistory(reportID) { }) .then((data) => { const indexedData = _.indexBy(data.history, 'sequenceNumber'); - Ion.set(`${IONKEYS.REPORT_HISTORY}_${reportID}`, indexedData); + Ion.set(`${IONKEYS.REPORT_ACTIONS}_${reportID}`, indexedData); }); } @@ -351,7 +351,7 @@ function fetchChatReport(participants) { * @returns {Promise} */ function addHistoryItem(reportID, reportComment) { - const historyKey = `${IONKEYS.REPORT_HISTORY}_${reportID}`; + const historyKey = `${IONKEYS.REPORT_ACTIONS}_${reportID}`; // Convert the comment from MD into HTML because that's how it is stored in the database const parser = new ExpensiMark(); diff --git a/src/page/home/report/ReportHistoryView.js b/src/page/home/report/ReportHistoryView.js index da9595c99b93..32eb167f321b 100644 --- a/src/page/home/report/ReportHistoryView.js +++ b/src/page/home/report/ReportHistoryView.js @@ -165,7 +165,7 @@ class ReportHistoryView extends React.Component { ReportHistoryView.propTypes = propTypes; ReportHistoryView.defaultProps = defaultProps; -const key = `${IONKEYS.REPORT_HISTORY}_%DATAFROMPROPS%`; +const key = `${IONKEYS.REPORT_ACTIONS}_%DATAFROMPROPS%`; export default compose( withRouter, withIon({ From 5494c537727f2bffb98dae7cf059d2a576da1828 Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Thu, 3 Sep 2020 14:02:02 -0600 Subject: [PATCH 13/31] Rename from history to action --- src/lib/actions/Report.js | 19 ++++++++++-------- ...storyCompose.js => ReportActionCompose.js} | 6 +++--- ...es.js => ReportActionFragmentPropTypes.js} | 0 ...portHistoryItem.js => ReportActionItem.js} | 16 +++++++-------- ...oryItemDate.js => ReportActionItemDate.js} | 8 ++++---- ...ragment.js => ReportActionItemFragment.js} | 10 +++++----- ...mGrouped.js => ReportActionItemGrouped.js} | 12 +++++------ ...mMessage.js => ReportActionItemMessage.js} | 14 ++++++------- ...temSingle.js => ReportActionItemSingle.js} | 20 +++++++++---------- ...ropsTypes.js => ReportActionPropsTypes.js} | 2 +- ...portHistoryView.js => ReportActionView.js} | 14 ++++++------- src/page/home/report/ReportView.js | 8 ++++---- 12 files changed, 66 insertions(+), 63 deletions(-) rename src/page/home/report/{ReportHistoryCompose.js => ReportActionCompose.js} (95%) rename src/page/home/report/{ReportHistoryFragmentPropTypes.js => ReportActionFragmentPropTypes.js} (100%) rename src/page/home/report/{ReportHistoryItem.js => ReportActionItem.js} (65%) rename src/page/home/report/{ReportHistoryItemDate.js => ReportActionItemDate.js} (70%) rename src/page/home/report/{ReportHistoryItemFragment.js => ReportActionItemFragment.js} (92%) rename src/page/home/report/{ReportHistoryItemGrouped.js => ReportActionItemGrouped.js} (63%) rename src/page/home/report/{ReportHistoryItemMessage.js => ReportActionItemMessage.js} (54%) rename src/page/home/report/{ReportHistoryItemSingle.js => ReportActionItemSingle.js} (70%) rename src/page/home/report/{ReportHistoryPropsTypes.js => ReportActionPropsTypes.js} (87%) rename src/page/home/report/{ReportHistoryView.js => ReportActionView.js} (95%) diff --git a/src/lib/actions/Report.js b/src/lib/actions/Report.js index da89f8878f3d..b5a13723c021 100644 --- a/src/lib/actions/Report.js +++ b/src/lib/actions/Report.js @@ -32,14 +32,6 @@ Ion.connect({key: `^${IONKEYS.PERSONAL_DETAILS}$`, callback: val => personalDeta let myPersonalDetails; Ion.connect({key: IONKEYS.MY_PERSONAL_DETAILS, callback: val => myPersonalDetails = val}); -// Listen for all reports added to Ion and if there is one that doesn't have a name, then -// fetch that report from the server so that it has all updated information about it -Ion.connect({key: `${IONKEYS.REPORT}_[0-9]+$`, callback: val => { - if (val && val.reportName === undefined) { - fetchChatReportsByIDs([val.reportID]); - } -}}); - const currentReportHistories = {}; Ion.connect({key: `${IONKEYS.REPORT_ACTIONS}_[0-9]+$`, callback: (val, key) => { currentReportHistories[key] = val; @@ -431,6 +423,17 @@ onReconnect(() => { fetchAll().then(reports => _.each(reports, report => fetchHistory(report.reportID))); }); +// Listen for all reports added to Ion and if there is one that doesn't have a name, then +// fetch that report from the server so that it has all updated information about it +Ion.connect({ + key: `${IONKEYS.REPORT}_[0-9]+$`, + callback: (val) => { + if (val && val.reportName === undefined) { + fetchChatReportsByIDs([val.reportID]); + } + } +}); + export { fetchAll, fetchHistory, diff --git a/src/page/home/report/ReportHistoryCompose.js b/src/page/home/report/ReportActionCompose.js similarity index 95% rename from src/page/home/report/ReportHistoryCompose.js rename to src/page/home/report/ReportActionCompose.js index b2706b2e938c..b49d0b0a8d92 100644 --- a/src/page/home/report/ReportHistoryCompose.js +++ b/src/page/home/report/ReportActionCompose.js @@ -10,7 +10,7 @@ const propTypes = { onSubmit: PropTypes.func.isRequired, }; -class ReportHistoryCompose extends React.Component { +class ReportActionCompose extends React.Component { constructor(props) { super(props); @@ -102,6 +102,6 @@ class ReportHistoryCompose extends React.Component { ); } } -ReportHistoryCompose.propTypes = propTypes; +ReportActionCompose.propTypes = propTypes; -export default ReportHistoryCompose; +export default ReportActionCompose; diff --git a/src/page/home/report/ReportHistoryFragmentPropTypes.js b/src/page/home/report/ReportActionFragmentPropTypes.js similarity index 100% rename from src/page/home/report/ReportHistoryFragmentPropTypes.js rename to src/page/home/report/ReportActionFragmentPropTypes.js diff --git a/src/page/home/report/ReportHistoryItem.js b/src/page/home/report/ReportActionItem.js similarity index 65% rename from src/page/home/report/ReportHistoryItem.js rename to src/page/home/report/ReportActionItem.js index 4ed1949243c8..a9ae577c3b6f 100644 --- a/src/page/home/report/ReportHistoryItem.js +++ b/src/page/home/report/ReportActionItem.js @@ -2,9 +2,9 @@ import React from 'react'; import {View} from 'react-native'; import PropTypes from 'prop-types'; import _ from 'underscore'; -import ReportHistoryItemSingle from './ReportHistoryItemSingle'; -import ReportHistoryPropsTypes from './ReportHistoryPropsTypes'; -import ReportHistoryItemGrouped from './ReportHistoryItemGrouped'; +import ReportActionItemSingle from './ReportActionItemSingle'; +import ReportHistoryPropsTypes from './ReportActionPropsTypes'; +import ReportActionItemGrouped from './ReportActionItemGrouped'; const propTypes = { // All the data of the history item @@ -14,7 +14,7 @@ const propTypes = { displayAsGroup: PropTypes.bool.isRequired, }; -class ReportHistoryItem extends React.Component { +class ReportActionItem extends React.Component { shouldComponentUpdate(nextProps) { // This component should only render if the history item's sequenceNumber or displayAsGroup props change return nextProps.displayAsGroup !== this.props.displayAsGroup @@ -29,13 +29,13 @@ class ReportHistoryItem extends React.Component { return ( - {!displayAsGroup && } - {displayAsGroup && } + {!displayAsGroup && } + {displayAsGroup && } ); } } -ReportHistoryItem.propTypes = propTypes; +ReportActionItem.propTypes = propTypes; -export default ReportHistoryItem; +export default ReportActionItem; diff --git a/src/page/home/report/ReportHistoryItemDate.js b/src/page/home/report/ReportActionItemDate.js similarity index 70% rename from src/page/home/report/ReportHistoryItemDate.js rename to src/page/home/report/ReportActionItemDate.js index 5c0f63888623..7e31d510d10f 100644 --- a/src/page/home/report/ReportHistoryItemDate.js +++ b/src/page/home/report/ReportActionItemDate.js @@ -9,13 +9,13 @@ const propTypes = { timestamp: PropTypes.number.isRequired, }; -const ReportHistoryItemDate = props => ( +const ReportActionItemDate = props => ( {DateUtils.timestampToDateTime(props.timestamp)} ); -ReportHistoryItemDate.propTypes = propTypes; -ReportHistoryItemDate.displayName = 'ReportHistoryItemDate'; +ReportActionItemDate.propTypes = propTypes; +ReportActionItemDate.displayName = 'ReportActionItemDate'; -export default ReportHistoryItemDate; +export default ReportActionItemDate; diff --git a/src/page/home/report/ReportHistoryItemFragment.js b/src/page/home/report/ReportActionItemFragment.js similarity index 92% rename from src/page/home/report/ReportHistoryItemFragment.js rename to src/page/home/report/ReportActionItemFragment.js index 587310e995c7..806746366fb2 100644 --- a/src/page/home/report/ReportHistoryItemFragment.js +++ b/src/page/home/report/ReportActionItemFragment.js @@ -2,7 +2,7 @@ import React from 'react'; import HTML from 'react-native-render-html'; import {Linking} from 'react-native'; import Str from '../../../lib/Str'; -import ReportHistoryFragmentPropTypes from './ReportHistoryFragmentPropTypes'; +import ReportHistoryFragmentPropTypes from './ReportActionFragmentPropTypes'; import styles, {webViewStyles} from '../../../style/StyleSheet'; import Text from '../../../components/Text'; import AnchorForCommentsOnly from '../../../components/AnchorForCommentsOnly'; @@ -13,7 +13,7 @@ const propTypes = { fragment: ReportHistoryFragmentPropTypes.isRequired, }; -class ReportHistoryItemFragment extends React.PureComponent { +class ReportActionItemFragment extends React.PureComponent { constructor(props) { super(props); @@ -96,7 +96,7 @@ class ReportHistoryItemFragment extends React.PureComponent { } } -ReportHistoryItemFragment.propTypes = propTypes; -ReportHistoryItemFragment.displayName = 'ReportHistoryItemFragment'; +ReportActionItemFragment.propTypes = propTypes; +ReportActionItemFragment.displayName = 'ReportActionItemFragment'; -export default ReportHistoryItemFragment; +export default ReportActionItemFragment; diff --git a/src/page/home/report/ReportHistoryItemGrouped.js b/src/page/home/report/ReportActionItemGrouped.js similarity index 63% rename from src/page/home/report/ReportHistoryItemGrouped.js rename to src/page/home/report/ReportActionItemGrouped.js index 1ba792322df5..34c45e93656b 100644 --- a/src/page/home/report/ReportHistoryItemGrouped.js +++ b/src/page/home/report/ReportActionItemGrouped.js @@ -1,8 +1,8 @@ import React from 'react'; import {View} from 'react-native'; import PropTypes from 'prop-types'; -import ReportHistoryPropsTypes from './ReportHistoryPropsTypes'; -import ReportHistoryItemMessage from './ReportHistoryItemMessage'; +import ReportHistoryPropsTypes from './ReportActionPropsTypes'; +import ReportActionItemMessage from './ReportActionItemMessage'; import styles from '../../../style/StyleSheet'; const propTypes = { @@ -10,14 +10,14 @@ const propTypes = { historyItem: PropTypes.shape(ReportHistoryPropsTypes).isRequired, }; -class ReportHistoryItemGrouped extends React.PureComponent { +class ReportActionItemGrouped extends React.PureComponent { render() { const {historyItem} = this.props; return ( - + @@ -25,6 +25,6 @@ class ReportHistoryItemGrouped extends React.PureComponent { } } -ReportHistoryItemGrouped.propTypes = propTypes; +ReportActionItemGrouped.propTypes = propTypes; -export default ReportHistoryItemGrouped; +export default ReportActionItemGrouped; diff --git a/src/page/home/report/ReportHistoryItemMessage.js b/src/page/home/report/ReportActionItemMessage.js similarity index 54% rename from src/page/home/report/ReportHistoryItemMessage.js rename to src/page/home/report/ReportActionItemMessage.js index be2aa04f691d..bdc82bf9cb6e 100644 --- a/src/page/home/report/ReportHistoryItemMessage.js +++ b/src/page/home/report/ReportActionItemMessage.js @@ -1,18 +1,18 @@ import React from 'react'; import PropTypes from 'prop-types'; import _ from 'underscore'; -import ReportHistoryItemFragment from './ReportHistoryItemFragment'; -import ReportHistoryPropsTypes from './ReportHistoryPropsTypes'; +import ReportActionItemFragment from './ReportActionItemFragment'; +import ReportHistoryPropsTypes from './ReportActionPropsTypes'; const propTypes = { // The report history item historyItem: PropTypes.shape(ReportHistoryPropsTypes).isRequired, }; -const ReportHistoryItemMessage = ({historyItem}) => ( +const ReportActionItemMessage = ({historyItem}) => ( <> {_.map(_.compact(historyItem.message), fragment => ( - @@ -20,7 +20,7 @@ const ReportHistoryItemMessage = ({historyItem}) => ( ); -ReportHistoryItemMessage.propTypes = propTypes; -ReportHistoryItemMessage.displayName = 'ReportHistoryItemMessage'; +ReportActionItemMessage.propTypes = propTypes; +ReportActionItemMessage.displayName = 'ReportActionItemMessage'; -export default ReportHistoryItemMessage; +export default ReportActionItemMessage; diff --git a/src/page/home/report/ReportHistoryItemSingle.js b/src/page/home/report/ReportActionItemSingle.js similarity index 70% rename from src/page/home/report/ReportHistoryItemSingle.js rename to src/page/home/report/ReportActionItemSingle.js index 44538c468c20..fcd355a5c12f 100644 --- a/src/page/home/report/ReportHistoryItemSingle.js +++ b/src/page/home/report/ReportActionItemSingle.js @@ -2,19 +2,19 @@ import React from 'react'; import {View, Image} from 'react-native'; import PropTypes from 'prop-types'; import _ from 'underscore'; -import ReportHistoryPropsTypes from './ReportHistoryPropsTypes'; -import ReportHistoryItemMessage from './ReportHistoryItemMessage'; -import ReportHistoryItemFragment from './ReportHistoryItemFragment'; +import ReportHistoryPropsTypes from './ReportActionPropsTypes'; +import ReportActionItemMessage from './ReportActionItemMessage'; +import ReportActionItemFragment from './ReportActionItemFragment'; import styles from '../../../style/StyleSheet'; import CONST from '../../../CONST'; -import ReportHistoryItemDate from './ReportHistoryItemDate'; +import ReportActionItemDate from './ReportActionItemDate'; const propTypes = { // All the data of the history item historyItem: PropTypes.shape(ReportHistoryPropsTypes).isRequired, }; -class ReportHistoryItemSingle extends React.PureComponent { +class ReportActionItemSingle extends React.PureComponent { render() { const {historyItem} = this.props; const avatarUrl = historyItem.automatic @@ -34,15 +34,15 @@ class ReportHistoryItemSingle extends React.PureComponent { {historyItem.person.map(fragment => ( - + ))} - + - + @@ -50,6 +50,6 @@ class ReportHistoryItemSingle extends React.PureComponent { } } -ReportHistoryItemSingle.propTypes = propTypes; +ReportActionItemSingle.propTypes = propTypes; -export default ReportHistoryItemSingle; +export default ReportActionItemSingle; diff --git a/src/page/home/report/ReportHistoryPropsTypes.js b/src/page/home/report/ReportActionPropsTypes.js similarity index 87% rename from src/page/home/report/ReportHistoryPropsTypes.js rename to src/page/home/report/ReportActionPropsTypes.js index a393fa3a9a8d..bd38134d4a8f 100644 --- a/src/page/home/report/ReportHistoryPropsTypes.js +++ b/src/page/home/report/ReportActionPropsTypes.js @@ -1,6 +1,6 @@ import PropTypes from 'prop-types'; -import HistoryFragmentPropTypes from './ReportHistoryFragmentPropTypes'; +import HistoryFragmentPropTypes from './ReportActionFragmentPropTypes'; export default { // Name of the action e.g. ADDCOMMENT diff --git a/src/page/home/report/ReportHistoryView.js b/src/page/home/report/ReportActionView.js similarity index 95% rename from src/page/home/report/ReportHistoryView.js rename to src/page/home/report/ReportActionView.js index 32eb167f321b..d630ebf6707c 100644 --- a/src/page/home/report/ReportHistoryView.js +++ b/src/page/home/report/ReportActionView.js @@ -8,10 +8,10 @@ import Ion from '../../../lib/Ion'; import withIon from '../../../components/withIon'; import {fetchHistory, updateLastReadActionID} from '../../../lib/actions/Report'; import IONKEYS from '../../../IONKEYS'; -import ReportHistoryItem from './ReportHistoryItem'; +import ReportActionItem from './ReportActionItem'; import styles from '../../../style/StyleSheet'; import {withRouter} from '../../../lib/Router'; -import ReportHistoryPropsTypes from './ReportHistoryPropsTypes'; +import ReportHistoryPropsTypes from './ReportActionPropsTypes'; import compose from '../../../lib/compose'; const propTypes = { @@ -28,7 +28,7 @@ const defaultProps = { reportHistory: {}, }; -class ReportHistoryView extends React.Component { +class ReportActionView extends React.Component { constructor(props) { super(props); @@ -151,7 +151,7 @@ class ReportHistoryView extends React.Component { contentContainerStyle={[styles.chatContentScrollView]} > {_.chain(this.props.reportHistory).sortBy('sequenceNumber').map((item, index) => ( - {shouldShowComposeForm && ( - addHistoryItem(this.props.reportID, text)} + addHistoryItem(this.props.reportID, text)} /> )} From 965f293ec50d3d3ee28ab039f3b107264da4b336 Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Thu, 3 Sep 2020 14:06:47 -0600 Subject: [PATCH 14/31] Rename history to actions --- src/lib/actions/Report.js | 54 +++++++++++------------ src/page/home/report/ReportActionView.js | 4 +- src/page/home/report/ReportView.js | 4 +- src/page/home/sidebar/ChatSwitcherView.js | 4 +- 4 files changed, 33 insertions(+), 33 deletions(-) diff --git a/src/lib/actions/Report.js b/src/lib/actions/Report.js index b5a13723c021..e7d38b618d73 100644 --- a/src/lib/actions/Report.js +++ b/src/lib/actions/Report.js @@ -32,26 +32,26 @@ Ion.connect({key: `^${IONKEYS.PERSONAL_DETAILS}$`, callback: val => personalDeta let myPersonalDetails; Ion.connect({key: IONKEYS.MY_PERSONAL_DETAILS, callback: val => myPersonalDetails = val}); -const currentReportHistories = {}; +const currentReportActions = {}; Ion.connect({key: `${IONKEYS.REPORT_ACTIONS}_[0-9]+$`, callback: (val, key) => { - currentReportHistories[key] = val; + currentReportActions[key] = val; }}); /* eslint-enable object-curly-newline,object-property-newline */ /** - * Checks the report to see if there are any unread history items + * Checks the report to see if there are any unread action items * * @param {string} accountID * @param {object} report * @returns {boolean} */ -function hasUnreadHistoryItems(accountID, report) { +function hasUnreadActions(accountID, report) { const usersLastReadActionID = lodashGet(report, ['reportNameValuePairs', `lastReadActionID_${accountID}`]); if (!usersLastReadActionID || report.reportActionList.length === 0) { return false; } - // Find the most recent sequence number from the report history + // Find the most recent sequence number from the report actions const lastReportAction = _.chain(report.reportActionList) .pluck('sequenceNumber') .max() @@ -80,7 +80,7 @@ function getSimplifiedReportObject(report) { reportID: report.reportID, reportName: report.reportName, reportNameValuePairs: report.reportNameValuePairs, - hasUnread: hasUnreadHistoryItems(currentUserAccountID, report), + hasUnread: hasUnreadActions(currentUserAccountID, report), }; } @@ -160,16 +160,16 @@ function updateReportWithNewAction(reportID, reportAction) { // from the code at the top of this file Ion.merge(`${IONKEYS.REPORT}_${reportID}`, {reportID}); - // Get the report history and return that to the next chain + // Get the report actions and return that to the next chain new Promise((resolve) => { - resolve(currentReportHistories[`${IONKEYS.REPORT_ACTIONS}_${reportID}`]); + resolve(currentReportActions[`${IONKEYS.REPORT_ACTIONS}_${reportID}`]); }) // Look to see if the report action from pusher already exists or not (it would exist if it's a comment just // written by the user). If the action doesn't exist, then update the unread flag on the report so the user // knows there is a new comment - .then((reportHistory) => { - if (reportHistory && !reportHistory[reportAction.sequenceNumber]) { + .then((reportActions) => { + if (reportActions && !reportActions[reportAction.sequenceNumber]) { Ion.merge(`${IONKEYS.REPORT}_${reportID}`, {hasUnread: true}); } }) @@ -276,12 +276,12 @@ function fetchAll() { } /** - * Get the history of a report + * Get the actions of a report * * @param {string} reportID * @returns {Promise} */ -function fetchHistory(reportID) { +function fetchActions(reportID) { return queueRequest('Report_GetHistory', { reportID, offset: 0, @@ -293,13 +293,13 @@ function fetchHistory(reportID) { } /** - * Get the chat report ID, and then the history, for a chat report for a specific + * Get the report ID, and then the actions, for a chat report for a specific * set of participants * * @param {string[]} participants * @returns {Promise} */ -function fetchChatReport(participants) { +function fetchOrCreateChatReport(participants) { let reportID; // Get the current users accountID and set it aside in a local variable @@ -336,29 +336,29 @@ function fetchChatReport(participants) { } /** - * Add a history item to a report + * Add an action item to a report * * @param {string} reportID - * @param {string} reportComment + * @param {string} text * @returns {Promise} */ -function addHistoryItem(reportID, reportComment) { - const historyKey = `${IONKEYS.REPORT_ACTIONS}_${reportID}`; +function addAction(reportID, text) { + const actionKey = `${IONKEYS.REPORT_ACTIONS}_${reportID}`; // Convert the comment from MD into HTML because that's how it is stored in the database const parser = new ExpensiMark(); - const htmlComment = parser.replace(reportComment); - const reportHistory = currentReportHistories[historyKey]; + const htmlComment = parser.replace(text); + const reportActions = currentReportActions[actionKey]; // The new sequence number will be one higher than the highest - let highestSequenceNumber = _.chain(reportHistory) + let highestSequenceNumber = _.chain(reportActions) .pluck('sequenceNumber') .max() .value() || 0; const newSequenceNumber = highestSequenceNumber + 1; // Optimistically add the new comment to the store before waiting to save it to the server - return Ion.merge(historyKey, { + return Ion.merge(actionKey, { [newSequenceNumber]: { actionName: 'ADDCOMMENT', actorEmail: currentUserEmail, @@ -418,9 +418,9 @@ function updateLastReadActionID(accountID, reportID, sequenceNumber) { })); } -// When the app reconnects from being offline, fetch all of the reports and their history +// When the app reconnects from being offline, fetch all of the reports and their actions onReconnect(() => { - fetchAll().then(reports => _.each(reports, report => fetchHistory(report.reportID))); + fetchAll().then(reports => _.each(reports, report => fetchActions(report.reportID))); }); // Listen for all reports added to Ion and if there is one that doesn't have a name, then @@ -436,9 +436,9 @@ Ion.connect({ export { fetchAll, - fetchHistory, - fetchChatReport, - addHistoryItem, + fetchActions, + fetchOrCreateChatReport, + addAction, updateLastReadActionID, subscribeToReportCommentEvents, }; diff --git a/src/page/home/report/ReportActionView.js b/src/page/home/report/ReportActionView.js index d630ebf6707c..33da203d67ea 100644 --- a/src/page/home/report/ReportActionView.js +++ b/src/page/home/report/ReportActionView.js @@ -6,7 +6,7 @@ import lodashGet from 'lodash.get'; import Text from '../../../components/Text'; import Ion from '../../../lib/Ion'; import withIon from '../../../components/withIon'; -import {fetchHistory, updateLastReadActionID} from '../../../lib/actions/Report'; +import {fetchActions, updateLastReadActionID} from '../../../lib/actions/Report'; import IONKEYS from '../../../IONKEYS'; import ReportActionItem from './ReportActionItem'; import styles from '../../../style/StyleSheet'; @@ -171,7 +171,7 @@ export default compose( withIon({ reportHistory: { key, - loader: fetchHistory, + loader: fetchActions, loaderParams: ['%DATAFROMPROPS%'], pathForProps: 'reportID', }, diff --git a/src/page/home/report/ReportView.js b/src/page/home/report/ReportView.js index 79694a36649d..05a91fd7bc18 100644 --- a/src/page/home/report/ReportView.js +++ b/src/page/home/report/ReportView.js @@ -3,7 +3,7 @@ import {View} from 'react-native'; import PropTypes from 'prop-types'; import ReportHistoryView from './ReportActionView'; import ReportActionCompose from './ReportActionCompose'; -import {addHistoryItem} from '../../../lib/actions/Report'; +import {addAction} from '../../../lib/actions/Report'; import KeyboardSpacer from '../../../components/KeyboardSpacer'; import styles from '../../../style/StyleSheet'; @@ -28,7 +28,7 @@ class ReportView extends React.PureComponent { {shouldShowComposeForm && ( addHistoryItem(this.props.reportID, text)} + onSubmit={text => addAction(this.props.reportID, text)} /> )} diff --git a/src/page/home/sidebar/ChatSwitcherView.js b/src/page/home/sidebar/ChatSwitcherView.js index 66598d0f6e96..f2208a3bee86 100644 --- a/src/page/home/sidebar/ChatSwitcherView.js +++ b/src/page/home/sidebar/ChatSwitcherView.js @@ -8,7 +8,7 @@ import Str from '../../../lib/Str'; import KeyboardShortcut from '../../../lib/KeyboardShortcut'; import ChatSwitcherList from './ChatSwitcherList'; import ChatSwitcherSearchForm from './ChatSwitcherSearchForm'; -import {fetchChatReport} from '../../../lib/actions/Report'; +import {fetchOrCreateChatReport} from '../../../lib/actions/Report'; const propTypes = { // A method that is triggered when the TextInput gets focus @@ -125,7 +125,7 @@ class ChatSwitcherView extends React.Component { */ fetchChatReportAndRedirect(option) { Ion.get(IONKEYS.MY_PERSONAL_DETAILS, 'login') - .then(currentLogin => fetchChatReport([currentLogin, option.login])) + .then(currentLogin => fetchOrCreateChatReport([currentLogin, option.login])) .then(reportID => Ion.set(IONKEYS.APP_REDIRECT_TO, `/${reportID}`)); this.reset(); } From 09821323c4109b3f89122d42623751fa2ffc3197 Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Thu, 3 Sep 2020 14:17:58 -0600 Subject: [PATCH 15/31] history renamed to action --- .../report/ReportActionFragmentPropTypes.js | 2 +- src/page/home/report/ReportActionItem.js | 18 +++++++------- .../home/report/ReportActionItemGrouped.js | 4 ++-- .../home/report/ReportActionItemMessage.js | 12 +++++----- .../home/report/ReportActionItemSingle.js | 24 +++++++++---------- ...PropsTypes.js => ReportActionPropTypes.js} | 0 src/page/home/report/ReportActionView.js | 4 ++-- src/page/home/report/ReportView.js | 4 ++-- 8 files changed, 34 insertions(+), 34 deletions(-) rename src/page/home/report/{ReportActionPropsTypes.js => ReportActionPropTypes.js} (100%) diff --git a/src/page/home/report/ReportActionFragmentPropTypes.js b/src/page/home/report/ReportActionFragmentPropTypes.js index 3dfdb0d8c085..51788260a798 100644 --- a/src/page/home/report/ReportActionFragmentPropTypes.js +++ b/src/page/home/report/ReportActionFragmentPropTypes.js @@ -1,7 +1,7 @@ import PropTypes from 'prop-types'; export default PropTypes.shape({ - // The type of the history fragment. Used to render a corresponding component + // The type of the action item fragment. Used to render a corresponding component type: PropTypes.string.isRequired, // The text content of the fragment. diff --git a/src/page/home/report/ReportActionItem.js b/src/page/home/report/ReportActionItem.js index a9ae577c3b6f..cf9407c77ecd 100644 --- a/src/page/home/report/ReportActionItem.js +++ b/src/page/home/report/ReportActionItem.js @@ -3,12 +3,12 @@ import {View} from 'react-native'; import PropTypes from 'prop-types'; import _ from 'underscore'; import ReportActionItemSingle from './ReportActionItemSingle'; -import ReportHistoryPropsTypes from './ReportActionPropsTypes'; +import ReportActionPropTypes from './ReportActionPropTypes'; import ReportActionItemGrouped from './ReportActionItemGrouped'; const propTypes = { - // All the data of the history item - historyItem: PropTypes.shape(ReportHistoryPropsTypes).isRequired, + // All the data of the action item + action: PropTypes.shape(ReportActionPropTypes).isRequired, // Should the comment have the appearance of being grouped with the previous comment? displayAsGroup: PropTypes.bool.isRequired, @@ -16,21 +16,21 @@ const propTypes = { class ReportActionItem extends React.Component { shouldComponentUpdate(nextProps) { - // This component should only render if the history item's sequenceNumber or displayAsGroup props change + // This component should only render if the action's sequenceNumber or displayAsGroup props change return nextProps.displayAsGroup !== this.props.displayAsGroup - || !_.isEqual(nextProps.historyItem, this.props.historyItem); + || !_.isEqual(nextProps.action, this.props.action); } render() { - const {historyItem, displayAsGroup} = this.props; - if (historyItem.actionName !== 'ADDCOMMENT') { + const {action, displayAsGroup} = this.props; + if (action.actionName !== 'ADDCOMMENT') { return null; } return ( - {!displayAsGroup && } - {displayAsGroup && } + {!displayAsGroup && } + {displayAsGroup && } ); } diff --git a/src/page/home/report/ReportActionItemGrouped.js b/src/page/home/report/ReportActionItemGrouped.js index 34c45e93656b..0663cb102cef 100644 --- a/src/page/home/report/ReportActionItemGrouped.js +++ b/src/page/home/report/ReportActionItemGrouped.js @@ -1,13 +1,13 @@ import React from 'react'; import {View} from 'react-native'; import PropTypes from 'prop-types'; -import ReportHistoryPropsTypes from './ReportActionPropsTypes'; +import ReportActionPropTypes from './ReportActionPropTypes'; import ReportActionItemMessage from './ReportActionItemMessage'; import styles from '../../../style/StyleSheet'; const propTypes = { // All the data of the history item - historyItem: PropTypes.shape(ReportHistoryPropsTypes).isRequired, + historyItem: PropTypes.shape(ReportActionPropTypes).isRequired, }; class ReportActionItemGrouped extends React.PureComponent { diff --git a/src/page/home/report/ReportActionItemMessage.js b/src/page/home/report/ReportActionItemMessage.js index bdc82bf9cb6e..7566e149750f 100644 --- a/src/page/home/report/ReportActionItemMessage.js +++ b/src/page/home/report/ReportActionItemMessage.js @@ -2,18 +2,18 @@ import React from 'react'; import PropTypes from 'prop-types'; import _ from 'underscore'; import ReportActionItemFragment from './ReportActionItemFragment'; -import ReportHistoryPropsTypes from './ReportActionPropsTypes'; +import ReportActionPropTypes from './ReportActionPropTypes'; const propTypes = { - // The report history item - historyItem: PropTypes.shape(ReportHistoryPropsTypes).isRequired, + // The report action + action: PropTypes.shape(ReportActionPropTypes).isRequired, }; -const ReportActionItemMessage = ({historyItem}) => ( +const ReportActionItemMessage = ({action}) => ( <> - {_.map(_.compact(historyItem.message), fragment => ( + {_.map(_.compact(action.message), fragment => ( ))} diff --git a/src/page/home/report/ReportActionItemSingle.js b/src/page/home/report/ReportActionItemSingle.js index fcd355a5c12f..abe3beeb690d 100644 --- a/src/page/home/report/ReportActionItemSingle.js +++ b/src/page/home/report/ReportActionItemSingle.js @@ -2,7 +2,7 @@ import React from 'react'; import {View, Image} from 'react-native'; import PropTypes from 'prop-types'; import _ from 'underscore'; -import ReportHistoryPropsTypes from './ReportActionPropsTypes'; +import ReportActionPropTypes from './ReportActionPropTypes'; import ReportActionItemMessage from './ReportActionItemMessage'; import ReportActionItemFragment from './ReportActionItemFragment'; import styles from '../../../style/StyleSheet'; @@ -10,39 +10,39 @@ import CONST from '../../../CONST'; import ReportActionItemDate from './ReportActionItemDate'; const propTypes = { - // All the data of the history item - historyItem: PropTypes.shape(ReportHistoryPropsTypes).isRequired, + // All the data of the action + action: PropTypes.shape(ReportActionPropTypes).isRequired, }; class ReportActionItemSingle extends React.PureComponent { render() { - const {historyItem} = this.props; - const avatarUrl = historyItem.automatic + const {action} = this.props; + const avatarUrl = action.automatic ? `${CONST.CLOUDFRONT_URL}/images/icons/concierge_2019.svg` - : historyItem.avatar; + : action.avatar; return ( - + - {historyItem.person.map(fragment => ( - + {action.person.map(fragment => ( + ))} - + - + diff --git a/src/page/home/report/ReportActionPropsTypes.js b/src/page/home/report/ReportActionPropTypes.js similarity index 100% rename from src/page/home/report/ReportActionPropsTypes.js rename to src/page/home/report/ReportActionPropTypes.js diff --git a/src/page/home/report/ReportActionView.js b/src/page/home/report/ReportActionView.js index 33da203d67ea..8963d2fcc91b 100644 --- a/src/page/home/report/ReportActionView.js +++ b/src/page/home/report/ReportActionView.js @@ -11,7 +11,7 @@ import IONKEYS from '../../../IONKEYS'; import ReportActionItem from './ReportActionItem'; import styles from '../../../style/StyleSheet'; import {withRouter} from '../../../lib/Router'; -import ReportHistoryPropsTypes from './ReportActionPropsTypes'; +import ReportActionPropTypes from './ReportActionPropTypes'; import compose from '../../../lib/compose'; const propTypes = { @@ -21,7 +21,7 @@ const propTypes = { /* Ion Props */ // Array of report history items for this report - reportHistory: PropTypes.PropTypes.objectOf(PropTypes.shape(ReportHistoryPropsTypes)), + reportHistory: PropTypes.PropTypes.objectOf(PropTypes.shape(ReportActionPropTypes)), }; const defaultProps = { diff --git a/src/page/home/report/ReportView.js b/src/page/home/report/ReportView.js index 05a91fd7bc18..b704d38b42a6 100644 --- a/src/page/home/report/ReportView.js +++ b/src/page/home/report/ReportView.js @@ -1,7 +1,7 @@ import React from 'react'; import {View} from 'react-native'; import PropTypes from 'prop-types'; -import ReportHistoryView from './ReportActionView'; +import ReportActionView from './ReportActionView'; import ReportActionCompose from './ReportActionCompose'; import {addAction} from '../../../lib/actions/Report'; import KeyboardSpacer from '../../../components/KeyboardSpacer'; @@ -24,7 +24,7 @@ class ReportView extends React.PureComponent { const shouldShowComposeForm = this.props.isActiveReport; return ( - + {shouldShowComposeForm && ( Date: Thu, 3 Sep 2020 14:23:10 -0600 Subject: [PATCH 16/31] Final round of renaming --- .../home/report/ReportActionItemFragment.js | 4 +- .../home/report/ReportActionItemGrouped.js | 8 ++-- src/page/home/report/ReportActionPropTypes.js | 8 ++-- src/page/home/report/ReportActionView.js | 40 +++++++++---------- src/page/home/sidebar/SidebarBottom.js | 2 +- src/style/StyleSheet.js | 6 +-- 6 files changed, 34 insertions(+), 34 deletions(-) diff --git a/src/page/home/report/ReportActionItemFragment.js b/src/page/home/report/ReportActionItemFragment.js index 806746366fb2..fe300c54d4ad 100644 --- a/src/page/home/report/ReportActionItemFragment.js +++ b/src/page/home/report/ReportActionItemFragment.js @@ -2,7 +2,7 @@ import React from 'react'; import HTML from 'react-native-render-html'; import {Linking} from 'react-native'; import Str from '../../../lib/Str'; -import ReportHistoryFragmentPropTypes from './ReportActionFragmentPropTypes'; +import ReportActionFragmentPropTypes from './ReportActionFragmentPropTypes'; import styles, {webViewStyles} from '../../../style/StyleSheet'; import Text from '../../../components/Text'; import AnchorForCommentsOnly from '../../../components/AnchorForCommentsOnly'; @@ -10,7 +10,7 @@ import {getAuthToken} from '../../../lib/Network'; const propTypes = { // The message fragment needing to be displayed - fragment: ReportHistoryFragmentPropTypes.isRequired, + fragment: ReportActionFragmentPropTypes.isRequired, }; class ReportActionItemFragment extends React.PureComponent { diff --git a/src/page/home/report/ReportActionItemGrouped.js b/src/page/home/report/ReportActionItemGrouped.js index 0663cb102cef..3ed21a4de5f7 100644 --- a/src/page/home/report/ReportActionItemGrouped.js +++ b/src/page/home/report/ReportActionItemGrouped.js @@ -6,18 +6,18 @@ import ReportActionItemMessage from './ReportActionItemMessage'; import styles from '../../../style/StyleSheet'; const propTypes = { - // All the data of the history item - historyItem: PropTypes.shape(ReportActionPropTypes).isRequired, + // All the data of the action + action: PropTypes.shape(ReportActionPropTypes).isRequired, }; class ReportActionItemGrouped extends React.PureComponent { render() { - const {historyItem} = this.props; + const {action} = this.props; return ( - + diff --git a/src/page/home/report/ReportActionPropTypes.js b/src/page/home/report/ReportActionPropTypes.js index bd38134d4a8f..84f216010caf 100644 --- a/src/page/home/report/ReportActionPropTypes.js +++ b/src/page/home/report/ReportActionPropTypes.js @@ -1,13 +1,13 @@ import PropTypes from 'prop-types'; -import HistoryFragmentPropTypes from './ReportActionFragmentPropTypes'; +import ReportActionFragmentPropTypes from './ReportActionFragmentPropTypes'; export default { // Name of the action e.g. ADDCOMMENT actionName: PropTypes.string.isRequired, // Person who created the action - person: PropTypes.arrayOf(HistoryFragmentPropTypes).isRequired, + person: PropTypes.arrayOf(ReportActionFragmentPropTypes).isRequired, // ID of the report action sequenceNumber: PropTypes.number.isRequired, @@ -15,6 +15,6 @@ export default { // Unix timestamp timestamp: PropTypes.number.isRequired, - // report history message - message: PropTypes.arrayOf(HistoryFragmentPropTypes).isRequired, + // report action message + message: PropTypes.arrayOf(ReportActionFragmentPropTypes).isRequired, }; diff --git a/src/page/home/report/ReportActionView.js b/src/page/home/report/ReportActionView.js index 8963d2fcc91b..f2f798495d2b 100644 --- a/src/page/home/report/ReportActionView.js +++ b/src/page/home/report/ReportActionView.js @@ -20,12 +20,12 @@ const propTypes = { /* Ion Props */ - // Array of report history items for this report - reportHistory: PropTypes.PropTypes.objectOf(PropTypes.shape(ReportActionPropTypes)), + // Array of report actions for this report + reportActions: PropTypes.PropTypes.objectOf(PropTypes.shape(ReportActionPropTypes)), }; const defaultProps = { - reportHistory: {}, + reportActions: {}, }; class ReportActionView extends React.Component { @@ -51,21 +51,21 @@ class ReportActionView extends React.Component { * Also checks to ensure that the comment is not too old to * be considered part of the same comment * - * @param {Number} historyItemIndex - index of the comment item in state to check + * @param {Number} actionIndex - index of the comment item in state to check * * @return {Boolean} */ // eslint-disable-next-line - isConsecutiveHistoryItemMadeByPreviousActor(historyItemIndex) { - const reportHistory = lodashGet(this.props, 'reportHistory', {}); + isConsecutiveActionMadeByPreviousActor(actionIndex) { + const reportActions = lodashGet(this.props, 'reportActions', {}); // This is the created action and the very first action so it cannot be a consecutive comment. - if (historyItemIndex === 0) { + if (actionIndex === 0) { return false; } - const previousAction = reportHistory[historyItemIndex - 1]; - const currentAction = reportHistory[historyItemIndex]; + const previousAction = reportActions[actionIndex - 1]; + const currentAction = reportActions[actionIndex]; // It's OK for there to be no previous action, and in that case, false will be returned // so that the comment isn't grouped @@ -91,8 +91,8 @@ class ReportActionView extends React.Component { * action when scrolled */ recordMaxAction() { - const reportHistory = lodashGet(this.props, 'reportHistory', {}); - const maxVisibleSequenceNumber = _.chain(reportHistory) + const reportActions = lodashGet(this.props, 'reportActions', {}); + const maxVisibleSequenceNumber = _.chain(reportActions) .pluck('sequenceNumber') .max() .value(); @@ -122,18 +122,18 @@ class ReportActionView extends React.Component { /** * 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 items in our history have changed since it was last rendered, then + * items have been rendered. If the number of actions have changed since it was last rendered, then * scroll the list to the end. */ scrollToListBottom() { - if (this.historyListElement) { - this.historyListElement.scrollToEnd({animated: false}); + if (this.actionListElement) { + this.actionListElement.scrollToEnd({animated: false}); } this.recordMaxAction(); } render() { - if (!_.size(this.props.reportHistory)) { + if (!_.size(this.props.reportActions)) { return ( Be the first person to comment! @@ -144,17 +144,17 @@ class ReportActionView extends React.Component { return ( { - this.historyListElement = el; + this.actionListElement = el; }} onContentSizeChange={this.scrollToListBottom} bounces={false} contentContainerStyle={[styles.chatContentScrollView]} > - {_.chain(this.props.reportHistory).sortBy('sequenceNumber').map((item, index) => ( + {_.chain(this.props.reportActions).sortBy('sequenceNumber').map((item, index) => ( )).value()} @@ -169,7 +169,7 @@ const key = `${IONKEYS.REPORT_ACTIONS}_%DATAFROMPROPS%`; export default compose( withRouter, withIon({ - reportHistory: { + reportActions: { key, loader: fetchActions, loaderParams: ['%DATAFROMPROPS%'], diff --git a/src/page/home/sidebar/SidebarBottom.js b/src/page/home/sidebar/SidebarBottom.js index 4bb589f5aa87..bc6901ffca9f 100644 --- a/src/page/home/sidebar/SidebarBottom.js +++ b/src/page/home/sidebar/SidebarBottom.js @@ -52,7 +52,7 @@ const SidebarBottom = ({myPersonalDetails, isOffline, insets}) => { diff --git a/src/style/StyleSheet.js b/src/style/StyleSheet.js index 0c50e0a85c6d..00693d09aca0 100644 --- a/src/style/StyleSheet.js +++ b/src/style/StyleSheet.js @@ -159,11 +159,11 @@ const styles = { color: colors.textReversed, }, - // History Items - historyItemAvatarWrapper: { + // Actions + actionAvatarWrapper: { width: 40, }, - historyItemAvatar: { + actionAvatar: { borderRadius: 20, height: 40, width: 40, From bf89214c571f88907310a3f040eac2b751c548ba Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Thu, 3 Sep 2020 14:46:04 -0600 Subject: [PATCH 17/31] Remove currentReportActions and simplify promises --- src/lib/actions/Report.js | 119 ++++++++++++++++++++------------------ 1 file changed, 64 insertions(+), 55 deletions(-) diff --git a/src/lib/actions/Report.js b/src/lib/actions/Report.js index e7d38b618d73..2376a7e9e7a5 100644 --- a/src/lib/actions/Report.js +++ b/src/lib/actions/Report.js @@ -31,13 +31,10 @@ Ion.connect({key: `^${IONKEYS.PERSONAL_DETAILS}$`, callback: val => personalDeta let myPersonalDetails; Ion.connect({key: IONKEYS.MY_PERSONAL_DETAILS, callback: val => myPersonalDetails = val}); - -const currentReportActions = {}; -Ion.connect({key: `${IONKEYS.REPORT_ACTIONS}_[0-9]+$`, callback: (val, key) => { - currentReportActions[key] = val; -}}); /* eslint-enable object-curly-newline,object-property-newline */ +const reportMaxSequenceNumbers = {}; + /** * Checks the report to see if there are any unread action items * @@ -158,48 +155,46 @@ function updateReportWithNewAction(reportID, reportAction) { // Always merge the reportID into Ion // If the report doesn't exist in Ion yet, then all the rest of the data will be filled out // from the code at the top of this file - Ion.merge(`${IONKEYS.REPORT}_${reportID}`, {reportID}); + Ion.merge(`${IONKEYS.REPORT}_${reportID}`, { + reportID, + maxSequenceNumber: reportAction.sequenceNumber, + }); - // Get the report actions and return that to the next chain - new Promise((resolve) => { - resolve(currentReportActions[`${IONKEYS.REPORT_ACTIONS}_${reportID}`]); - }) + const previousMaxSequenceNumber = reportMaxSequenceNumbers[reportID]; + const newMaxSequenceNumber = reportAction.sequenceNumber; - // Look to see if the report action from pusher already exists or not (it would exist if it's a comment just - // written by the user). If the action doesn't exist, then update the unread flag on the report so the user - // knows there is a new comment - .then((reportActions) => { - if (reportActions && !reportActions[reportAction.sequenceNumber]) { - Ion.merge(`${IONKEYS.REPORT}_${reportID}`, {hasUnread: true}); - } - }) + // Mark the report as unread if there is a new max sequence number + if (newMaxSequenceNumber > previousMaxSequenceNumber) { + Ion.merge(`${IONKEYS.REPORT}_${reportID}`, { + hasUnread: true, + maxSequenceNumber: newMaxSequenceNumber, + }); + } - // Merge the new action into Ion - .then(() => Ion.merge(`${IONKEYS.REPORT_ACTIONS}_${reportID}`, { - [reportAction.sequenceNumber]: reportAction, - })) + // Add the action into Ion + Ion.merge(`${IONKEYS.REPORT_ACTIONS}_${reportID}`, { + [reportAction.sequenceNumber]: reportAction, + }); - .then(() => { - // If this comment is from the current user we don't want to parrot whatever they wrote back to them. - if (reportAction.actorEmail === currentUserEmail) { - return; - } - - const currentReportID = Number(lodashGet(currentURL.split('/'), [1], 0)); - - // If we are currently viewing this report do not show a notification. - if (reportID === currentReportID) { - return; - } - - Notification.showCommentNotification({ - reportAction, - onClick: () => { - // Navigate to this report onClick - Ion.set(IONKEYS.APP_REDIRECT_TO, `/${reportID}`); - } - }); - }); + // If this comment is from the current user we don't want to parrot whatever they wrote back to them. + if (reportAction.actorEmail === currentUserEmail) { + return; + } + + const currentReportID = Number(lodashGet(currentURL.split('/'), [1], 0)); + + // If we are currently viewing this report do not show a notification. + if (reportID === currentReportID) { + return; + } + + Notification.showCommentNotification({ + reportAction, + onClick: () => { + // Navigate to this report onClick + Ion.set(IONKEYS.APP_REDIRECT_TO, `/${reportID}`); + } + }); } /** @@ -288,7 +283,12 @@ function fetchActions(reportID) { }) .then((data) => { const indexedData = _.indexBy(data.history, 'sequenceNumber'); + const maxSequenceNumber = _.chain(data.history) + .pluck('sequenceNumber') + .max() + .value(); Ion.set(`${IONKEYS.REPORT_ACTIONS}_${reportID}`, indexedData); + Ion.merge(`${IONKEYS.REPORT}_${reportID}`, {maxSequenceNumber}); }); } @@ -340,7 +340,6 @@ function fetchOrCreateChatReport(participants) { * * @param {string} reportID * @param {string} text - * @returns {Promise} */ function addAction(reportID, text) { const actionKey = `${IONKEYS.REPORT_ACTIONS}_${reportID}`; @@ -348,17 +347,18 @@ function addAction(reportID, text) { // Convert the comment from MD into HTML because that's how it is stored in the database const parser = new ExpensiMark(); const htmlComment = parser.replace(text); - const reportActions = currentReportActions[actionKey]; // The new sequence number will be one higher than the highest - let highestSequenceNumber = _.chain(reportActions) - .pluck('sequenceNumber') - .max() - .value() || 0; + let highestSequenceNumber = reportMaxSequenceNumbers[reportID] || 0; const newSequenceNumber = highestSequenceNumber + 1; + // Update the report in Ion to have the new sequence number + Ion.merge(`${IONKEYS.REPORT}_${reportID}`, { + maxSequenceNumber: newSequenceNumber, + }); + // Optimistically add the new comment to the store before waiting to save it to the server - return Ion.merge(actionKey, { + Ion.merge(actionKey, { [newSequenceNumber]: { actionName: 'ADDCOMMENT', actorEmail: currentUserEmail, @@ -385,11 +385,12 @@ function addAction(reportID, text) { isFirstItem: false, isAttachmentPlaceHolder: false, } - }) - .then(() => queueRequest('Report_AddComment', { - reportID, - reportComment: htmlComment, - })); + }); + + queueRequest('Report_AddComment', { + reportID, + reportComment: htmlComment, + }); } /** @@ -428,9 +429,17 @@ onReconnect(() => { Ion.connect({ key: `${IONKEYS.REPORT}_[0-9]+$`, callback: (val) => { + // Nothing can be done without a report ID and it's OK to fail gracefully + if (!val.reportID) { + return; + } + if (val && val.reportName === undefined) { fetchChatReportsByIDs([val.reportID]); } + + // Keep a local copy of all the max sequence numbers for reports + reportMaxSequenceNumbers[val.reportID] = val.maxSequenceNumber; } }); From 59911d9f9c873e4683305b64154a0565599e04b9 Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Thu, 3 Sep 2020 14:52:28 -0600 Subject: [PATCH 18/31] Make the unread flag clear if you're looking at a chat when a comment comes in --- ...eportActionView.js => ReportActionsView.js} | 18 ++++++++++++++---- src/page/home/report/ReportView.js | 2 +- 2 files changed, 15 insertions(+), 5 deletions(-) rename src/page/home/report/{ReportActionView.js => ReportActionsView.js} (89%) diff --git a/src/page/home/report/ReportActionView.js b/src/page/home/report/ReportActionsView.js similarity index 89% rename from src/page/home/report/ReportActionView.js rename to src/page/home/report/ReportActionsView.js index f2f798495d2b..601c2058d4fa 100644 --- a/src/page/home/report/ReportActionView.js +++ b/src/page/home/report/ReportActionsView.js @@ -28,12 +28,13 @@ const defaultProps = { reportActions: {}, }; -class ReportActionView extends React.Component { +class ReportActionsView extends React.Component { constructor(props) { super(props); this.recordlastReadActionID = _.debounce(this.recordlastReadActionID.bind(this), 1000, true); this.scrollToListBottom = this.scrollToListBottom.bind(this); + this.recordMaxAction = this.recordMaxAction.bind(this); } componentDidMount() { @@ -44,6 +45,14 @@ class ReportActionView extends React.Component { this.keyboardEvent.remove(); } + componentDidUpdate(prevProps) { + // When the number of actions change, wait three seconds, then record the max action + // This will make the unread indicator go away if you receive comments in the same chat you're looking at + if (_.size(prevProps.reportActions) !== _.size(this.props.reportActions)){ + setTimeout(this.recordMaxAction, 3000); + } + } + /** * Returns true when the report action immediately before the * specified index is a comment made by the same actor who who @@ -114,6 +123,7 @@ class ReportActionView extends React.Component { return Ion.get(`${IONKEYS.REPORT}_${this.props.reportID}`, path, 0); }) .then((lastReadActionID) => { + console.log('unread', maxSequenceNumber, lastReadActionID); if (maxSequenceNumber > lastReadActionID) { updateLastReadActionID(myAccountID, this.props.reportID, maxSequenceNumber); } @@ -162,8 +172,8 @@ class ReportActionView extends React.Component { } } -ReportActionView.propTypes = propTypes; -ReportActionView.defaultProps = defaultProps; +ReportActionsView.propTypes = propTypes; +ReportActionsView.defaultProps = defaultProps; const key = `${IONKEYS.REPORT_ACTIONS}_%DATAFROMPROPS%`; export default compose( @@ -176,4 +186,4 @@ export default compose( pathForProps: 'reportID', }, }), -)(ReportActionView); +)(ReportActionsView); diff --git a/src/page/home/report/ReportView.js b/src/page/home/report/ReportView.js index b704d38b42a6..6478ca8244d5 100644 --- a/src/page/home/report/ReportView.js +++ b/src/page/home/report/ReportView.js @@ -1,7 +1,7 @@ import React from 'react'; import {View} from 'react-native'; import PropTypes from 'prop-types'; -import ReportActionView from './ReportActionView'; +import ReportActionView from './ReportActionsView'; import ReportActionCompose from './ReportActionCompose'; import {addAction} from '../../../lib/actions/Report'; import KeyboardSpacer from '../../../components/KeyboardSpacer'; From 2711ff3d25f11f7a483ad342dd109a327c31cb49 Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Thu, 3 Sep 2020 21:56:05 -0600 Subject: [PATCH 19/31] Simplify more promise chains --- src/lib/actions/Report.js | 88 ++++++++++++----------- src/page/home/report/ReportActionsView.js | 9 ++- 2 files changed, 51 insertions(+), 46 deletions(-) diff --git a/src/lib/actions/Report.js b/src/lib/actions/Report.js index 2376a7e9e7a5..519aca4ecb95 100644 --- a/src/lib/actions/Report.js +++ b/src/lib/actions/Report.js @@ -125,9 +125,12 @@ function fetchChatReportsByIDs(chatList) { .unique() .value(); - return PersonalDetails.getForEmails(emails.join(',')); - }) - .then(() => { + // Fetch the person details if there are any + if (emails && emails.length !== 0) { + PersonalDetails.getForEmails(emails.join(',')); + } + + // Process the reports and store them in Ion const ionPromises = _.map(fetchedReports, (report) => { const newReport = getSimplifiedReportObject(report); @@ -246,28 +249,30 @@ function fetchAll() { reportFetchPromises.push(fetchChatReports()); return promiseAllSettled(reportFetchPromises) - .then(data => fetchedReports = _.compact(_.map(data, (promiseResult) => { - // Grab the report from the promise result which stores it in the `value` key - const report = lodashGet(promiseResult, 'value.reports', {}); - - // If there is no report found from the promise, return null - // Otherwise, grab the actual report object from the first index in the values array - return _.isEmpty(report) ? null : _.values(report)[0]; - }))) - .then(() => Ion.set(IONKEYS.FIRST_REPORT_ID, _.first(_.pluck(fetchedReports, 'reportID')) || 0)) - .then(() => { - const ionPromises = _.map(fetchedReports, (report) => { + .then((data) => { + fetchedReports = _.compact(_.map(data, (promiseResult) => { + // Grab the report from the promise result which stores it in the `value` key + const report = lodashGet(promiseResult, 'value.reports', {}); + + // If there is no report found from the promise, return null + // Otherwise, grab the actual report object from the first index in the values array + return _.isEmpty(report) ? null : _.values(report)[0]; + })); + + // Store the first report ID in Ion + Ion.set(IONKEYS.FIRST_REPORT_ID, _.first(_.pluck(fetchedReports, 'reportID')) || 0); + + _.each(fetchedReports, (report) => { // Store only the absolute bare minimum of data in Ion because space is limited const newReport = getSimplifiedReportObject(report); // Merge the data into Ion. Don't use set() here or multiSet() because then that would // overwrite any existing data (like if they have unread messages) - return Ion.merge(`${IONKEYS.REPORT}_${report.reportID}`, newReport); + Ion.merge(`${IONKEYS.REPORT}_${report.reportID}`, newReport); }); - return promiseAllSettled(ionPromises); - }) - .then(() => fetchedReports); + return fetchedReports; + }); } /** @@ -308,15 +313,17 @@ function fetchOrCreateChatReport(participants) { emailList: participants.join(','), }) - // Set aside the reportID in a local variable so it can be accessed in the rest of the chain - .then(data => reportID = data.reportID) - - // Make a request to get all the information about the report - .then(() => queueRequest('Get', { - returnValueList: 'reportStuff', - reportIDList: reportID, - shouldLoadOptionalKeys: true, - })) + .then((data) => { + // Set aside the reportID in a local variable so it can be accessed in the rest of the chain + reportID = data.reportID; + + // Make a request to get all the information about the report + return queueRequest('Get', { + returnValueList: 'reportStuff', + reportIDList: reportID, + shouldLoadOptionalKeys: true, + }); + }) // Put the report object into Ion .then((data) => { @@ -328,11 +335,11 @@ function fetchOrCreateChatReport(participants) { // Merge the data into Ion. Don't use set() here or multiSet() because then that would // overwrite any existing data (like if they have unread messages) - return Ion.merge(`${IONKEYS.REPORT}_${reportID}`, newReport); - }) + Ion.merge(`${IONKEYS.REPORT}_${reportID}`, newReport); - // Return the reportID as the final return value - .then(() => reportID); + // Return the reportID as the final return value + return reportID; + }); } /** @@ -400,23 +407,23 @@ function addAction(reportID, text) { * @param {string} accountID * @param {string} reportID * @param {number} sequenceNumber - * @returns {Promise} */ function updateLastReadActionID(accountID, reportID, sequenceNumber) { // Mark the report as not having any unread items - return Ion.merge(`${IONKEYS.REPORT}_${reportID}`, { + Ion.merge(`${IONKEYS.REPORT}_${reportID}`, { hasUnread: false, reportNameValuePairs: { [`lastReadActionID_${accountID}`]: sequenceNumber, } - }) + }); + - // Update the lastReadActionID on the report optimistically - .then(() => queueRequest('Report_SetLastReadActionID', { - accountID, - reportID, - sequenceNumber, - })); + // Update the lastReadActionID on the report optimistically + queueRequest('Report_SetLastReadActionID', { + accountID, + reportID, + sequenceNumber, + }); } // When the app reconnects from being offline, fetch all of the reports and their actions @@ -442,7 +449,6 @@ Ion.connect({ reportMaxSequenceNumbers[val.reportID] = val.maxSequenceNumber; } }); - export { fetchAll, fetchActions, diff --git a/src/page/home/report/ReportActionsView.js b/src/page/home/report/ReportActionsView.js index 601c2058d4fa..2573f4dde89b 100644 --- a/src/page/home/report/ReportActionsView.js +++ b/src/page/home/report/ReportActionsView.js @@ -41,10 +41,6 @@ class ReportActionsView extends React.Component { this.keyboardEvent = Keyboard.addListener('keyboardDidShow', this.scrollToListBottom); } - componentWillUnmount() { - this.keyboardEvent.remove(); - } - componentDidUpdate(prevProps) { // When the number of actions change, wait three seconds, then record the max action // This will make the unread indicator go away if you receive comments in the same chat you're looking at @@ -53,6 +49,10 @@ class ReportActionsView extends React.Component { } } + componentWillUnmount() { + this.keyboardEvent.remove(); + } + /** * Returns true when the report action immediately before the * specified index is a comment made by the same actor who who @@ -123,7 +123,6 @@ class ReportActionsView extends React.Component { return Ion.get(`${IONKEYS.REPORT}_${this.props.reportID}`, path, 0); }) .then((lastReadActionID) => { - console.log('unread', maxSequenceNumber, lastReadActionID); if (maxSequenceNumber > lastReadActionID) { updateLastReadActionID(myAccountID, this.props.reportID, maxSequenceNumber); } From b166e5b7a00e29bc9f6eb015fec2abc7e91ac30d Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Thu, 3 Sep 2020 22:07:03 -0600 Subject: [PATCH 20/31] Correct merge issues and fix unread chats --- src/lib/actions/Report.js | 27 ++++------------------- src/page/home/report/ReportActionsView.js | 2 +- 2 files changed, 5 insertions(+), 24 deletions(-) diff --git a/src/lib/actions/Report.js b/src/lib/actions/Report.js index b44a32f287f1..ab7032c38652 100644 --- a/src/lib/actions/Report.js +++ b/src/lib/actions/Report.js @@ -52,17 +52,15 @@ function hasUnreadActions(accountID, report) { } // Find the most recent sequence number from the report history - const sequenceNumber = _.chain(report.reportActionList) - .pluck('sequenceNumber') - .max() - .value(); + const maxSequenceNumber = reportMaxSequenceNumbers[report.reportID]; - if (!sequenceNumber) { + if (!maxSequenceNumber) { return false; } + console.log('unread', maxSequenceNumber, usersLastReadActionID); // There are unread items if the last one the user has read is less than the highest sequence number we have - return usersLastReadActionID < sequenceNumber; + return usersLastReadActionID < maxSequenceNumber; } /** @@ -152,23 +150,6 @@ function fetchChatReportsByIDs(chatList) { }); } -/** - * Get the history of a report - * - * @param {string} reportID - * @returns {Promise} - */ -function fetchHistory(reportID) { - return queueRequest('Report_GetHistory', { - reportID, - offset: 0, - }) - .then((data) => { - const indexedData = _.indexBy(data.history, 'sequenceNumber'); - Ion.set(`${IONKEYS.REPORT_HISTORY}_${reportID}`, indexedData); - }); -} - /** * Updates a report in the store with a new report action * diff --git a/src/page/home/report/ReportActionsView.js b/src/page/home/report/ReportActionsView.js index 2573f4dde89b..3001cca31380 100644 --- a/src/page/home/report/ReportActionsView.js +++ b/src/page/home/report/ReportActionsView.js @@ -44,7 +44,7 @@ class ReportActionsView extends React.Component { componentDidUpdate(prevProps) { // When the number of actions change, wait three seconds, then record the max action // This will make the unread indicator go away if you receive comments in the same chat you're looking at - if (_.size(prevProps.reportActions) !== _.size(this.props.reportActions)){ + if (_.size(prevProps.reportActions) !== _.size(this.props.reportActions)) { setTimeout(this.recordMaxAction, 3000); } } From 71bb62ccec8ccb112988a5b503e74fcf3e826772 Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Thu, 3 Sep 2020 22:11:17 -0600 Subject: [PATCH 21/31] Remove console log --- src/lib/actions/Report.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/actions/Report.js b/src/lib/actions/Report.js index ab7032c38652..829e9892a83e 100644 --- a/src/lib/actions/Report.js +++ b/src/lib/actions/Report.js @@ -57,7 +57,6 @@ function hasUnreadActions(accountID, report) { if (!maxSequenceNumber) { return false; } - console.log('unread', maxSequenceNumber, usersLastReadActionID); // There are unread items if the last one the user has read is less than the highest sequence number we have return usersLastReadActionID < maxSequenceNumber; From efe41917e67a69f64800ebb20db1cbdb9d77aba0 Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Thu, 3 Sep 2020 22:20:16 -0600 Subject: [PATCH 22/31] Remove unnecessary param and simplify the ion callback --- src/lib/actions/Report.js | 51 +++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 21 deletions(-) diff --git a/src/lib/actions/Report.js b/src/lib/actions/Report.js index 829e9892a83e..b670a71ed69d 100644 --- a/src/lib/actions/Report.js +++ b/src/lib/actions/Report.js @@ -41,12 +41,14 @@ const configReportIDs = CONFIG.REPORT_IDS.split(',').map(Number); /** * Checks the report to see if there are any unread action items * - * @param {string} accountID * @param {object} report * @returns {boolean} */ -function hasUnreadActions(accountID, report) { - const usersLastReadActionID = lodashGet(report, ['reportNameValuePairs', `lastReadActionID_${accountID}`]); +function hasUnreadActions(report) { + const usersLastReadActionID = lodashGet(report, [ + 'reportNameValuePairs', + `lastReadActionID_${currentUserAccountID}`, + ]); if (!usersLastReadActionID || report.reportActionList.length === 0) { return false; } @@ -77,7 +79,7 @@ function getSimplifiedReportObject(report) { reportID: report.reportID, reportName: report.reportName, reportNameValuePairs: report.reportNameValuePairs, - hasUnread: hasUnreadActions(currentUserAccountID, report), + hasUnread: hasUnreadActions(report), pinnedReport: configReportIDs.includes(report.reportID), }; } @@ -158,7 +160,7 @@ function fetchChatReportsByIDs(chatList) { function updateReportWithNewAction(reportID, reportAction) { // Always merge the reportID into Ion // If the report doesn't exist in Ion yet, then all the rest of the data will be filled out - // from the code at the top of this file + // by handleReportChanged Ion.merge(`${IONKEYS.REPORT}_${reportID}`, { reportID, maxSequenceNumber: reportAction.sequenceNumber, @@ -427,28 +429,35 @@ function updateLastReadActionID(accountID, reportID, sequenceNumber) { }); } -// When the app reconnects from being offline, fetch all of the reports and their actions -onReconnect(() => { - fetchAll().then(reports => _.each(reports, report => fetchActions(report.reportID))); -}); +/** + * When a report changes in Ion, this fetches the report from the API if the report doesn't have a name + * and it keeps track of the max sequence number on the report actions. + * + * @param {object} report + */ +function handleReportChanged(report) { + // Nothing can be done without a report ID and it's OK to fail gracefully + if (!report.reportID) { + return; + } + + if (report && report.reportName === undefined) { + fetchChatReportsByIDs([val.reportID]); + } + + reportMaxSequenceNumbers[val.reportID] = val.maxSequenceNumber; +} // Listen for all reports added to Ion and if there is one that doesn't have a name, then // fetch that report from the server so that it has all updated information about it Ion.connect({ key: `${IONKEYS.REPORT}_[0-9]+$`, - callback: (val) => { - // Nothing can be done without a report ID and it's OK to fail gracefully - if (!val.reportID) { - return; - } - - if (val && val.reportName === undefined) { - fetchChatReportsByIDs([val.reportID]); - } + callback: handleReportChanged +}); - // Keep a local copy of all the max sequence numbers for reports - reportMaxSequenceNumbers[val.reportID] = val.maxSequenceNumber; - } +// When the app reconnects from being offline, fetch all of the reports and their actions +onReconnect(() => { + fetchAll().then(reports => _.each(reports, report => fetchActions(report.reportID))); }); export { fetchAll, From a65eecb79a5a367d5bf0fb10a6e78a4898996eb3 Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Thu, 3 Sep 2020 22:22:19 -0600 Subject: [PATCH 23/31] Remove unnecessary comment --- src/lib/actions/Report.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/lib/actions/Report.js b/src/lib/actions/Report.js index b670a71ed69d..8d06b2e84c8d 100644 --- a/src/lib/actions/Report.js +++ b/src/lib/actions/Report.js @@ -447,9 +447,6 @@ function handleReportChanged(report) { reportMaxSequenceNumbers[val.reportID] = val.maxSequenceNumber; } - -// Listen for all reports added to Ion and if there is one that doesn't have a name, then -// fetch that report from the server so that it has all updated information about it Ion.connect({ key: `${IONKEYS.REPORT}_[0-9]+$`, callback: handleReportChanged From 3441d79bd4f70dcfdd6189997085e8905ee783a3 Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Thu, 3 Sep 2020 22:28:49 -0600 Subject: [PATCH 24/31] Fix bad variables --- src/lib/actions/Report.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/actions/Report.js b/src/lib/actions/Report.js index 8d06b2e84c8d..4d069d4d4cc1 100644 --- a/src/lib/actions/Report.js +++ b/src/lib/actions/Report.js @@ -442,10 +442,10 @@ function handleReportChanged(report) { } if (report && report.reportName === undefined) { - fetchChatReportsByIDs([val.reportID]); + fetchChatReportsByIDs([report.reportID]); } - reportMaxSequenceNumbers[val.reportID] = val.maxSequenceNumber; + reportMaxSequenceNumbers[report.reportID] = report.maxSequenceNumber; } Ion.connect({ key: `${IONKEYS.REPORT}_[0-9]+$`, From 884f9ba17632360cff1195b32fe3667a0a41a0a5 Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Fri, 4 Sep 2020 12:43:52 -0600 Subject: [PATCH 25/31] camelcase the keys --- src/IONKEYS.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/IONKEYS.js b/src/IONKEYS.js index 2a345c9b235b..366339719a57 100644 --- a/src/IONKEYS.js +++ b/src/IONKEYS.js @@ -2,15 +2,15 @@ * This is a file containing constants for all the top level keys in our store */ export default { - ACTIVE_CLIENTS: 'active_clients', - APP_REDIRECT_TO: 'app_redirectTo', - CURRENT_URL: 'current_url', + ACTIVE_CLIENTS: 'activeClients', + APP_REDIRECT_TO: 'appRedirectTo', + CURRENT_URL: 'currentURL', CREDENTIALS: 'credentials', - FIRST_REPORT_ID: 'first_report_id', - MY_PERSONAL_DETAILS: 'my_personal_details', + FIRST_REPORT_ID: 'firstReportID', + MY_PERSONAL_DETAILS: 'myPersonalDetails', NETWORK: 'network', - PERSONAL_DETAILS: 'personal_details', + PERSONAL_DETAILS: 'personalDetails', REPORT: 'report', - REPORT_ACTIONS: 'report_actions', + REPORT_ACTIONS: 'reportActions', SESSION: 'session', }; From 5e2a96c81ddf6c055137f1ea9fb6fc3c6f8548fe Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Fri, 4 Sep 2020 12:44:02 -0600 Subject: [PATCH 26/31] remove unnecessary variable and offset --- src/lib/actions/Report.js | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/lib/actions/Report.js b/src/lib/actions/Report.js index 4d069d4d4cc1..65cd7d07d5b3 100644 --- a/src/lib/actions/Report.js +++ b/src/lib/actions/Report.js @@ -266,12 +266,9 @@ function fetchAll() { Ion.set(IONKEYS.FIRST_REPORT_ID, _.first(_.pluck(fetchedReports, 'reportID')) || 0); _.each(fetchedReports, (report) => { - // Store only the absolute bare minimum of data in Ion because space is limited - const newReport = getSimplifiedReportObject(report); - // Merge the data into Ion. Don't use set() here or multiSet() because then that would // overwrite any existing data (like if they have unread messages) - Ion.merge(`${IONKEYS.REPORT}_${report.reportID}`, newReport); + Ion.merge(`${IONKEYS.REPORT}_${report.reportID}`, getSimplifiedReportObject(report)); }); return fetchedReports; @@ -285,10 +282,7 @@ function fetchAll() { * @returns {Promise} */ function fetchActions(reportID) { - return queueRequest('Report_GetHistory', { - reportID, - offset: 0, - }) + return queueRequest('Report_GetHistory', {reportID}) .then((data) => { const indexedData = _.indexBy(data.history, 'sequenceNumber'); const maxSequenceNumber = _.chain(data.history) From 7fc2e7e773be1a9e150994bcda62ff2018f650b9 Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Fri, 4 Sep 2020 12:58:58 -0600 Subject: [PATCH 27/31] Fix some unread bugs --- src/lib/actions/Report.js | 14 +++++++------- src/page/home/report/ReportActionsView.js | 4 +++- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/src/lib/actions/Report.js b/src/lib/actions/Report.js index 65cd7d07d5b3..34be8fa57a76 100644 --- a/src/lib/actions/Report.js +++ b/src/lib/actions/Report.js @@ -49,10 +49,15 @@ function hasUnreadActions(report) { 'reportNameValuePairs', `lastReadActionID_${currentUserAccountID}`, ]); - if (!usersLastReadActionID || report.reportActionList.length === 0) { + + if (report.reportActionList.length === 0) { return false; } + if (!usersLastReadActionID) { + return true; + } + // Find the most recent sequence number from the report history const maxSequenceNumber = reportMaxSequenceNumbers[report.reportID]; @@ -430,12 +435,7 @@ function updateLastReadActionID(accountID, reportID, sequenceNumber) { * @param {object} report */ function handleReportChanged(report) { - // Nothing can be done without a report ID and it's OK to fail gracefully - if (!report.reportID) { - return; - } - - if (report && report.reportName === undefined) { + if (report.reportName === undefined) { fetchChatReportsByIDs([report.reportID]); } diff --git a/src/page/home/report/ReportActionsView.js b/src/page/home/report/ReportActionsView.js index 3001cca31380..a3b211e9018c 100644 --- a/src/page/home/report/ReportActionsView.js +++ b/src/page/home/report/ReportActionsView.js @@ -42,9 +42,11 @@ class ReportActionsView extends React.Component { } componentDidUpdate(prevProps) { + const isReportVisible = this.props.reportID === this.props.match.params.reportID; + // When the number of actions change, wait three seconds, then record the max action // This will make the unread indicator go away if you receive comments in the same chat you're looking at - if (_.size(prevProps.reportActions) !== _.size(this.props.reportActions)) { + if (isReportVisible && _.size(prevProps.reportActions) !== _.size(this.props.reportActions)) { setTimeout(this.recordMaxAction, 3000); } } From f315341f66ab514db771bea3e5f7b669b64f8385 Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Fri, 4 Sep 2020 13:10:53 -0600 Subject: [PATCH 28/31] Fixing several things from PR review --- src/lib/actions/Report.js | 44 +++++++++++++---------- src/page/home/MainView.js | 2 +- src/page/home/report/ReportActionsView.js | 6 +++- src/page/home/sidebar/SidebarLink.js | 4 --- src/page/home/sidebar/SidebarLinks.js | 4 ++- 5 files changed, 35 insertions(+), 25 deletions(-) diff --git a/src/lib/actions/Report.js b/src/lib/actions/Report.js index 34be8fa57a76..ecc5b03f012b 100644 --- a/src/lib/actions/Report.js +++ b/src/lib/actions/Report.js @@ -11,27 +11,37 @@ import ExpensiMark from '../ExpensiMark'; import Notification from '../Notification'; import * as PersonalDetails from './PersonalDetails'; -// Disabling these rules makes the following code more compact and easier to read for these instances -/* eslint-disable object-curly-newline,object-property-newline */ let currentUserEmail; let currentUserAccountID; -Ion.connect({key: IONKEYS.SESSION, callback: (val) => { - if (val) { - currentUserEmail = val.email; - currentUserAccountID = val.accountID; +Ion.connect({ + key: IONKEYS.SESSION, + callback: (val) => { + // When signed out, val is undefined + if (val) { + currentUserEmail = val.email; + currentUserAccountID = val.accountID; + } } -}}); +}); let currentURL; -Ion.connect({key: IONKEYS.CURRENT_URL, callback: val => currentURL = val}); +Ion.connect({ + key: IONKEYS.CURRENT_URL, + callback: val => currentURL = val, +}); // Use a regex pattern here for an exact match so it doesn't also match "my_personal_details" let personalDetails; -Ion.connect({key: `^${IONKEYS.PERSONAL_DETAILS}$`, callback: val => personalDetails = val}); +Ion.connect({ + key: `^${IONKEYS.PERSONAL_DETAILS}$`, + callback: val => personalDetails = val, +}); let myPersonalDetails; -Ion.connect({key: IONKEYS.MY_PERSONAL_DETAILS, callback: val => myPersonalDetails = val}); -/* eslint-enable object-curly-newline,object-property-newline */ +Ion.connect({ + key: IONKEYS.MY_PERSONAL_DETAILS, + callback: val => myPersonalDetails = val, +}); const reportMaxSequenceNumbers = {}; @@ -159,7 +169,7 @@ function fetchChatReportsByIDs(chatList) { /** * Updates a report in the store with a new report action * - * @param {string} reportID + * @param {number} reportID * @param {object} reportAction */ function updateReportWithNewAction(reportID, reportAction) { @@ -283,7 +293,7 @@ function fetchAll() { /** * Get the actions of a report * - * @param {string} reportID + * @param {number} reportID * @returns {Promise} */ function fetchActions(reportID) { @@ -304,13 +314,11 @@ function fetchActions(reportID) { * set of participants * * @param {string[]} participants - * @returns {Promise} + * @returns {Promise} resolves with reportID */ function fetchOrCreateChatReport(participants) { let reportID; - // Get the current users accountID and set it aside in a local variable - // which is used for checking if there are unread comments return queueRequest('CreateChatReport', { emailList: participants.join(','), }) @@ -347,7 +355,7 @@ function fetchOrCreateChatReport(participants) { /** * Add an action item to a report * - * @param {string} reportID + * @param {number} reportID * @param {string} text */ function addAction(reportID, text) { @@ -407,7 +415,7 @@ function addAction(reportID, text) { * network layer handle the delayed write. * * @param {string} accountID - * @param {string} reportID + * @param {number} reportID * @param {number} sequenceNumber */ function updateLastReadActionID(accountID, reportID, sequenceNumber) { diff --git a/src/page/home/MainView.js b/src/page/home/MainView.js index f22553a8125c..80b9eb97bfc2 100644 --- a/src/page/home/MainView.js +++ b/src/page/home/MainView.js @@ -55,7 +55,7 @@ class MainView extends React.Component { return ( <> - {_.map(this.props.reports, report => report.reportID && ( + {_.map(this.props.reports, report => ( { - if (!props.reportName) { - return null; - } - const reportIDInUrl = parseInt(props.match.params.reportID, 10); const isReportActive = reportIDInUrl === props.reportID; const linkWrapperActiveStyle = isReportActive && styles.sidebarLinkWrapperActive; diff --git a/src/page/home/sidebar/SidebarLinks.js b/src/page/home/sidebar/SidebarLinks.js index 40493a20bec9..8c8fb271f5de 100644 --- a/src/page/home/sidebar/SidebarLinks.js +++ b/src/page/home/sidebar/SidebarLinks.js @@ -89,7 +89,9 @@ class SidebarLinks extends React.Component { Chats - {_.map(reportsToDisplay, report => ( + {/* A report will not have a report name if it hasn't been fetched from the server yet */} + {/* so nothing is rendered */} + {_.map(reportsToDisplay, report => report.reportName && ( Date: Fri, 4 Sep 2020 13:12:06 -0600 Subject: [PATCH 29/31] Remove a history refernce --- src/lib/actions/Report.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/actions/Report.js b/src/lib/actions/Report.js index ecc5b03f012b..c185e880cf0e 100644 --- a/src/lib/actions/Report.js +++ b/src/lib/actions/Report.js @@ -68,7 +68,7 @@ function hasUnreadActions(report) { return true; } - // Find the most recent sequence number from the report history + // Find the most recent sequence number from the report actions const maxSequenceNumber = reportMaxSequenceNumbers[report.reportID]; if (!maxSequenceNumber) { From 68596da2fa90af6dc817bef715f7ef6741172448 Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Fri, 4 Sep 2020 14:01:28 -0600 Subject: [PATCH 30/31] Adding debug info for notifications --- src/lib/actions/Report.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib/actions/Report.js b/src/lib/actions/Report.js index c185e880cf0e..fcdede6a9167 100644 --- a/src/lib/actions/Report.js +++ b/src/lib/actions/Report.js @@ -199,6 +199,7 @@ function updateReportWithNewAction(reportID, reportAction) { // If this comment is from the current user we don't want to parrot whatever they wrote back to them. if (reportAction.actorEmail === currentUserEmail) { + console.debug('[NOTIFICATION] No notification because comment is from the currently logged in user'); return; } @@ -206,9 +207,11 @@ function updateReportWithNewAction(reportID, reportAction) { // If we are currently viewing this report do not show a notification. if (reportID === currentReportID) { + console.debug('[NOTIFICATION] No notification because it was a comment for the current report'); return; } + console.debug('[NOTIFICATION] Creating notification'); Notification.showCommentNotification({ reportAction, onClick: () => { From d11b4960b7bd1e64613702f8fac1f8df730dd520 Mon Sep 17 00:00:00 2001 From: Tim Golen Date: Fri, 4 Sep 2020 14:01:52 -0600 Subject: [PATCH 31/31] Shorten line --- src/page/home/report/ReportActionCompose.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/page/home/report/ReportActionCompose.js b/src/page/home/report/ReportActionCompose.js index 13944dd11e2c..8eefa7333b5b 100644 --- a/src/page/home/report/ReportActionCompose.js +++ b/src/page/home/report/ReportActionCompose.js @@ -85,7 +85,7 @@ class ReportActionCompose extends React.Component { onKeyPress={this.triggerSubmitShortcut} style={[styles.textInput, styles.textInputCompose, styles.flex4]} value={this.state.comment} - maxLines={16} // This is the same that slack has, but in all honesty we can choose a different value + maxLines={16} // This is the same that slack has />