diff --git a/src/IONKEYS.js b/src/IONKEYS.js
index 6f8204e68152..366339719a57 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',
- APP_REDIRECT_TO: 'app_redirectTo',
- CURRENT_URL: 'current_url',
+ ACTIVE_CLIENTS: 'activeClients',
+ APP_REDIRECT_TO: 'appRedirectTo',
+ CURRENT_URL: 'currentURL',
CREDENTIALS: 'credentials',
- 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_ACTION: 'reportAction',
- REPORT_HISTORY: 'report_history',
- FIRST_REPORT_ID: 'first_report_id',
+ REPORT_ACTIONS: 'reportActions',
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/Network.js b/src/lib/Network.js
index 9c05d296c0a0..dbad2332c86e 100644
--- a/src/lib/Network.js
+++ b/src/lib/Network.js
@@ -16,10 +16,10 @@ let isOffline;
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 928e9521e7fd..fcdede6a9167 100644
--- a/src/lib/actions/Report.js
+++ b/src/lib/actions/Report.js
@@ -11,34 +11,72 @@ import ExpensiMark from '../ExpensiMark';
import Notification from '../Notification';
import * as PersonalDetails from './PersonalDetails';
+let currentUserEmail;
+let currentUserAccountID;
+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,
+});
+
+// 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 reportMaxSequenceNumbers = {};
+
// List of reportIDs that we define in .env
const configReportIDs = CONFIG.REPORT_IDS.split(',').map(Number);
/**
- * 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) {
- const usersLastReadActionID = lodashGet(report, ['reportNameValuePairs', `lastReadActionID_${accountID}`]);
- if (!usersLastReadActionID || report.reportActionList.length === 0) {
+function hasUnreadActions(report) {
+ const usersLastReadActionID = lodashGet(report, [
+ 'reportNameValuePairs',
+ `lastReadActionID_${currentUserAccountID}`,
+ ]);
+
+ if (report.reportActionList.length === 0) {
return false;
}
- // Find the most recent sequence number from the report history
- const sequenceNumber = _.chain(report.reportActionList)
- .pluck('sequenceNumber')
- .max()
- .value();
+ if (!usersLastReadActionID) {
+ return true;
+ }
+
+ // Find the most recent sequence number from the report actions
+ const maxSequenceNumber = reportMaxSequenceNumbers[report.reportID];
- if (!sequenceNumber) {
+ if (!maxSequenceNumber) {
return false;
}
// 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;
}
/**
@@ -49,15 +87,14 @@ 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,
reportNameValuePairs: report.reportNameValuePairs,
- hasUnread: hasUnreadHistoryItems(accountID, report),
+ hasUnread: hasUnreadActions(report),
pinnedReport: configReportIDs.includes(report.reportID),
};
}
@@ -66,11 +103,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)
@@ -87,19 +122,12 @@ function getChatReportName(sharedReportList, personalDetails, currentUserEmail)
* @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;
@@ -115,20 +143,18 @@ function fetchChatReportsByIDs(chatList) {
.unique()
.value();
- return PersonalDetails.getForEmails(emails.join(','));
- })
- .then((personalDetails) => {
+ // 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) => {
- // Store only the absolute bare minimum of data in Ion because space is limited
- 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
@@ -140,123 +166,73 @@ 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
*
- * @param {string} reportID
+ * @param {number} reportID
* @param {object} reportAction
*/
function updateReportWithNewAction(reportID, reportAction) {
- let currentUserEmail;
- let ionReportFound = true;
-
- 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');
- }
-
- // 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) {
- ionReportFound = false;
- return fetchChatReportsByIDs([reportID])
- .then(() => fetchHistory(reportID))
- .then(() => Ion.get(`${IONKEYS.REPORT_HISTORY}_${reportID}`));
- }
-
- // Get the report history and return that to the next chain
- return Ion.get(`${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
+ // by handleReportChanged
+ Ion.merge(`${IONKEYS.REPORT}_${reportID}`, {
+ reportID,
+ maxSequenceNumber: 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((reportHistory) => {
- // If there was no ionReport found then we cannot check sequence number because the fetchHistory in the
- // previous block will give us the most up to date information.
- if (!ionReportFound || (reportHistory && !reportHistory[reportAction.sequenceNumber])) {
- Ion.merge(`${IONKEYS.REPORT}_${reportID}`, {hasUnread: true});
- }
- return reportHistory || {};
- })
+ const previousMaxSequenceNumber = reportMaxSequenceNumbers[reportID];
+ const newMaxSequenceNumber = reportAction.sequenceNumber;
- // Put the report action from pusher into the history, it's OK to overwrite it if it already exists
- .then(reportHistory => ({
- ...reportHistory,
- [reportAction.sequenceNumber]: reportAction,
- }))
+ // 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,
+ });
+ }
- // Put the report history back into Ion
- .then(reportHistory => Ion.set(`${IONKEYS.REPORT_HISTORY}_${reportID}`, reportHistory))
+ // Add the action into Ion
+ Ion.merge(`${IONKEYS.REPORT_ACTIONS}_${reportID}`, {
+ [reportAction.sequenceNumber]: reportAction,
+ });
- // 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) => {
- // 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;
- }
+ // 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;
+ }
- 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) {
- return;
- }
+ // 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;
+ }
- Notification.showCommentNotification({
- reportAction,
- onClick: () => {
- // Navigate to this report onClick
- Ion.set(IONKEYS.APP_REDIRECT_TO, `/${reportID}`);
- }
- });
- });
+ console.debug('[NOTIFICATION] Creating notification');
+ Notification.showCommentNotification({
+ reportAction,
+ onClick: () => {
+ // Navigate to this report onClick
+ Ion.set(IONKEYS.APP_REDIRECT_TO, `/${reportID}`);
+ }
+ });
}
/**
* 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}`;
- 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);
+ });
}
/**
@@ -294,147 +270,147 @@ 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(() => Ion.get(IONKEYS.SESSION, 'accountID'))
- .then((accountID) => {
- const ionPromises = _.map(fetchedReports, (report) => {
- // Store only the absolute bare minimum of data in Ion because space is limited
- const newReport = getSimplifiedReportObject(report, accountID);
+ .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) => {
// 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}`, getSimplifiedReportObject(report));
});
- return promiseAllSettled(ionPromises);
- })
- .then(() => fetchedReports);
+ return fetchedReports;
+ });
+}
+
+/**
+ * Get the actions of a report
+ *
+ * @param {number} reportID
+ * @returns {Promise}
+ */
+function fetchActions(reportID) {
+ return queueRequest('Report_GetHistory', {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});
+ });
}
/**
- * 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}
+ * @returns {Promise} resolves with reportID
*/
-function fetchChatReport(participants) {
- let currentUserEmail;
- let currentUserAccountID;
- let personalDetails;
+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 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)
+ .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
- .then(() => queueRequest('Get', {
- returnValueList: 'reportStuff',
- reportIDList: reportID,
- shouldLoadOptionalKeys: true,
- }))
+ // 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) => {
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)
- 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;
+ });
}
/**
- * Add a history item to a report
+ * Add an action item to a report
*
- * @param {string} reportID
- * @param {string} reportComment
- * @returns {Promise}
+ * @param {number} reportID
+ * @param {string} text
*/
-function addHistoryItem(reportID, reportComment) {
- const historyKey = `${IONKEYS.REPORT_HISTORY}_${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);
-
- 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], {});
-
- // 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: email,
- person: [
- {
- style: 'strong',
- text: personalDetails.displayName || email,
- type: 'TEXT'
- }
- ],
- automatic: false,
- sequenceNumber: ++highestSequenceNumber,
- avatar: personalDetails.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,
+ const htmlComment = parser.replace(text);
+
+ // The new sequence number will be one higher than the highest
+ 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
+ Ion.merge(actionKey, {
+ [newSequenceNumber]: {
+ actionName: 'ADDCOMMENT',
+ actorEmail: currentUserEmail,
+ person: [
+ {
+ style: 'strong',
+ text: myPersonalDetails.displayName || currentUserEmail,
+ type: 'TEXT'
}
- });
- })
- .then(() => queueRequest('Report_AddComment', {
- reportID,
- reportComment: htmlComment,
- }));
+ ],
+ 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,
+ }
+ });
+
+ queueRequest('Report_AddComment', {
+ reportID,
+ reportComment: htmlComment,
+ });
}
/**
@@ -442,37 +418,54 @@ function addHistoryItem(reportID, reportComment) {
* network layer handle the delayed write.
*
* @param {string} accountID
- * @param {string} reportID
+ * @param {number} 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 history
-onReconnect(() => {
- fetchAll().then(reports => _.each(reports, report => fetchHistory(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) {
+ if (report.reportName === undefined) {
+ fetchChatReportsByIDs([report.reportID]);
+ }
+
+ reportMaxSequenceNumbers[report.reportID] = report.maxSequenceNumber;
+}
+Ion.connect({
+ key: `${IONKEYS.REPORT}_[0-9]+$`,
+ callback: handleReportChanged
});
+// 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,
- fetchHistory,
- fetchChatReport,
- addHistoryItem,
+ fetchActions,
+ fetchOrCreateChatReport,
+ addAction,
updateLastReadActionID,
subscribeToReportCommentEvents,
};
diff --git a/src/page/home/report/ReportHistoryCompose.js b/src/page/home/report/ReportActionCompose.js
similarity index 91%
rename from src/page/home/report/ReportHistoryCompose.js
rename to src/page/home/report/ReportActionCompose.js
index ca6a97674c8f..8eefa7333b5b 100644
--- a/src/page/home/report/ReportHistoryCompose.js
+++ b/src/page/home/report/ReportActionCompose.js
@@ -8,12 +8,9 @@ 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 {
+class ReportActionCompose extends React.Component {
constructor(props) {
super(props);
@@ -69,7 +66,7 @@ class ReportHistoryCompose extends React.Component {
return;
}
- this.props.onSubmit(this.props.reportID, trimmedComment);
+ this.props.onSubmit(trimmedComment);
this.setState({
comment: '',
});
@@ -106,6 +103,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 90%
rename from src/page/home/report/ReportHistoryFragmentPropTypes.js
rename to src/page/home/report/ReportActionFragmentPropTypes.js
index 3dfdb0d8c085..51788260a798 100644
--- a/src/page/home/report/ReportHistoryFragmentPropTypes.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
new file mode 100644
index 000000000000..cf9407c77ecd
--- /dev/null
+++ b/src/page/home/report/ReportActionItem.js
@@ -0,0 +1,41 @@
+import React from 'react';
+import {View} from 'react-native';
+import PropTypes from 'prop-types';
+import _ from 'underscore';
+import ReportActionItemSingle from './ReportActionItemSingle';
+import ReportActionPropTypes from './ReportActionPropTypes';
+import ReportActionItemGrouped from './ReportActionItemGrouped';
+
+const propTypes = {
+ // 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,
+};
+
+class ReportActionItem extends React.Component {
+ shouldComponentUpdate(nextProps) {
+ // This component should only render if the action's sequenceNumber or displayAsGroup props change
+ return nextProps.displayAsGroup !== this.props.displayAsGroup
+ || !_.isEqual(nextProps.action, this.props.action);
+ }
+
+ render() {
+ const {action, displayAsGroup} = this.props;
+ if (action.actionName !== 'ADDCOMMENT') {
+ return null;
+ }
+
+ return (
+
+ {!displayAsGroup && }
+ {displayAsGroup && }
+
+ );
+ }
+}
+
+ReportActionItem.propTypes = propTypes;
+
+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 91%
rename from src/page/home/report/ReportHistoryItemFragment.js
rename to src/page/home/report/ReportActionItemFragment.js
index 160837922a7f..aa0b8703b983 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 ReportActionFragmentPropTypes from './ReportActionFragmentPropTypes';
import styles, {webViewStyles} from '../../../style/StyleSheet';
import Text from '../../../components/Text';
import AnchorForCommentsOnly from '../../../components/AnchorForCommentsOnly';
@@ -10,10 +10,10 @@ import {getAuthToken} from '../../../lib/Network';
const propTypes = {
// The message fragment needing to be displayed
- fragment: ReportHistoryFragmentPropTypes.isRequired,
+ fragment: ReportActionFragmentPropTypes.isRequired,
};
-class ReportHistoryItemFragment extends React.PureComponent {
+class ReportActionItemFragment extends React.PureComponent {
constructor(props) {
super(props);
@@ -100,7 +100,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/ReportActionItemGrouped.js b/src/page/home/report/ReportActionItemGrouped.js
new file mode 100644
index 000000000000..3ed21a4de5f7
--- /dev/null
+++ b/src/page/home/report/ReportActionItemGrouped.js
@@ -0,0 +1,30 @@
+import React from 'react';
+import {View} from 'react-native';
+import PropTypes from 'prop-types';
+import ReportActionPropTypes from './ReportActionPropTypes';
+import ReportActionItemMessage from './ReportActionItemMessage';
+import styles from '../../../style/StyleSheet';
+
+const propTypes = {
+ // All the data of the action
+ action: PropTypes.shape(ReportActionPropTypes).isRequired,
+};
+
+class ReportActionItemGrouped extends React.PureComponent {
+ render() {
+ const {action} = this.props;
+ return (
+
+
+
+
+
+
+
+ );
+ }
+}
+
+ReportActionItemGrouped.propTypes = propTypes;
+
+export default ReportActionItemGrouped;
diff --git a/src/page/home/report/ReportActionItemMessage.js b/src/page/home/report/ReportActionItemMessage.js
new file mode 100644
index 000000000000..7566e149750f
--- /dev/null
+++ b/src/page/home/report/ReportActionItemMessage.js
@@ -0,0 +1,26 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import _ from 'underscore';
+import ReportActionItemFragment from './ReportActionItemFragment';
+import ReportActionPropTypes from './ReportActionPropTypes';
+
+const propTypes = {
+ // The report action
+ action: PropTypes.shape(ReportActionPropTypes).isRequired,
+};
+
+const ReportActionItemMessage = ({action}) => (
+ <>
+ {_.map(_.compact(action.message), fragment => (
+
+ ))}
+ >
+);
+
+ReportActionItemMessage.propTypes = propTypes;
+ReportActionItemMessage.displayName = 'ReportActionItemMessage';
+
+export default ReportActionItemMessage;
diff --git a/src/page/home/report/ReportActionItemSingle.js b/src/page/home/report/ReportActionItemSingle.js
new file mode 100644
index 000000000000..abe3beeb690d
--- /dev/null
+++ b/src/page/home/report/ReportActionItemSingle.js
@@ -0,0 +1,55 @@
+import React from 'react';
+import {View, Image} from 'react-native';
+import PropTypes from 'prop-types';
+import _ from 'underscore';
+import ReportActionPropTypes from './ReportActionPropTypes';
+import ReportActionItemMessage from './ReportActionItemMessage';
+import ReportActionItemFragment from './ReportActionItemFragment';
+import styles from '../../../style/StyleSheet';
+import CONST from '../../../CONST';
+import ReportActionItemDate from './ReportActionItemDate';
+
+const propTypes = {
+ // All the data of the action
+ action: PropTypes.shape(ReportActionPropTypes).isRequired,
+};
+
+class ReportActionItemSingle extends React.PureComponent {
+ render() {
+ const {action} = this.props;
+ const avatarUrl = action.automatic
+ ? `${CONST.CLOUDFRONT_URL}/images/icons/concierge_2019.svg`
+ : action.avatar;
+ return (
+
+
+
+
+
+
+
+
+ {action.person.map(fragment => (
+
+
+
+ ))}
+
+
+
+
+
+
+
+
+
+ );
+ }
+}
+
+ReportActionItemSingle.propTypes = propTypes;
+
+export default ReportActionItemSingle;
diff --git a/src/page/home/report/ReportHistoryPropsTypes.js b/src/page/home/report/ReportActionPropTypes.js
similarity index 56%
rename from src/page/home/report/ReportHistoryPropsTypes.js
rename to src/page/home/report/ReportActionPropTypes.js
index a393fa3a9a8d..84f216010caf 100644
--- a/src/page/home/report/ReportHistoryPropsTypes.js
+++ b/src/page/home/report/ReportActionPropTypes.js
@@ -1,13 +1,13 @@
import PropTypes from 'prop-types';
-import HistoryFragmentPropTypes from './ReportHistoryFragmentPropTypes';
+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/ReportHistoryView.js b/src/page/home/report/ReportActionsView.js
similarity index 66%
rename from src/page/home/report/ReportHistoryView.js
rename to src/page/home/report/ReportActionsView.js
index da9595c99b93..0c241ed98fa8 100644
--- a/src/page/home/report/ReportHistoryView.js
+++ b/src/page/home/report/ReportActionsView.js
@@ -6,40 +6,55 @@ 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 ReportHistoryItem from './ReportHistoryItem';
+import ReportActionItem from './ReportActionItem';
import styles from '../../../style/StyleSheet';
import {withRouter} from '../../../lib/Router';
-import ReportHistoryPropsTypes from './ReportHistoryPropsTypes';
+import ReportActionPropTypes from './ReportActionPropTypes';
import compose from '../../../lib/compose';
const propTypes = {
+ // These are from withRouter
+ // eslint-disable-next-line react/forbid-prop-types
+ match: PropTypes.object.isRequired,
+
// The ID of the report actions will be created for
reportID: PropTypes.number.isRequired,
/* Ion Props */
- // Array of report history items for this report
- reportHistory: PropTypes.PropTypes.objectOf(PropTypes.shape(ReportHistoryPropsTypes)),
+ // Array of report actions for this report
+ reportActions: PropTypes.PropTypes.objectOf(PropTypes.shape(ReportActionPropTypes)),
};
const defaultProps = {
- reportHistory: {},
+ reportActions: {},
};
-class ReportHistoryView 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() {
this.keyboardEvent = Keyboard.addListener('keyboardDidShow', this.scrollToListBottom);
}
+ 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 (isReportVisible && _.size(prevProps.reportActions) !== _.size(this.props.reportActions)) {
+ setTimeout(this.recordMaxAction, 3000);
+ }
+ }
+
componentWillUnmount() {
this.keyboardEvent.remove();
}
@@ -51,21 +66,21 @@ class ReportHistoryView 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 +106,8 @@ class ReportHistoryView 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 +137,18 @@ class ReportHistoryView 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 has 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 +159,17 @@ class ReportHistoryView 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) => (
- (
+
)).value()}
@@ -162,18 +177,18 @@ class ReportHistoryView extends React.Component {
}
}
-ReportHistoryView.propTypes = propTypes;
-ReportHistoryView.defaultProps = defaultProps;
+ReportActionsView.propTypes = propTypes;
+ReportActionsView.defaultProps = defaultProps;
-const key = `${IONKEYS.REPORT_HISTORY}_%DATAFROMPROPS%`;
+const key = `${IONKEYS.REPORT_ACTIONS}_%DATAFROMPROPS%`;
export default compose(
withRouter,
withIon({
- reportHistory: {
+ reportActions: {
key,
- loader: fetchHistory,
+ loader: fetchActions,
loaderParams: ['%DATAFROMPROPS%'],
pathForProps: 'reportID',
},
}),
-)(ReportHistoryView);
+)(ReportActionsView);
diff --git a/src/page/home/report/ReportHistoryItem.js b/src/page/home/report/ReportHistoryItem.js
deleted file mode 100644
index 4ed1949243c8..000000000000
--- a/src/page/home/report/ReportHistoryItem.js
+++ /dev/null
@@ -1,41 +0,0 @@
-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';
-
-const propTypes = {
- // All the data of the history item
- historyItem: PropTypes.shape(ReportHistoryPropsTypes).isRequired,
-
- // Should the comment have the appearance of being grouped with the previous comment?
- displayAsGroup: PropTypes.bool.isRequired,
-};
-
-class ReportHistoryItem 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
- || !_.isEqual(nextProps.historyItem, this.props.historyItem);
- }
-
- render() {
- const {historyItem, displayAsGroup} = this.props;
- if (historyItem.actionName !== 'ADDCOMMENT') {
- return null;
- }
-
- return (
-
- {!displayAsGroup && }
- {displayAsGroup && }
-
- );
- }
-}
-
-ReportHistoryItem.propTypes = propTypes;
-
-export default ReportHistoryItem;
diff --git a/src/page/home/report/ReportHistoryItemGrouped.js b/src/page/home/report/ReportHistoryItemGrouped.js
deleted file mode 100644
index 1ba792322df5..000000000000
--- a/src/page/home/report/ReportHistoryItemGrouped.js
+++ /dev/null
@@ -1,30 +0,0 @@
-import React from 'react';
-import {View} from 'react-native';
-import PropTypes from 'prop-types';
-import ReportHistoryPropsTypes from './ReportHistoryPropsTypes';
-import ReportHistoryItemMessage from './ReportHistoryItemMessage';
-import styles from '../../../style/StyleSheet';
-
-const propTypes = {
- // All the data of the history item
- historyItem: PropTypes.shape(ReportHistoryPropsTypes).isRequired,
-};
-
-class ReportHistoryItemGrouped extends React.PureComponent {
- render() {
- const {historyItem} = this.props;
- return (
-
-
-
-
-
-
-
- );
- }
-}
-
-ReportHistoryItemGrouped.propTypes = propTypes;
-
-export default ReportHistoryItemGrouped;
diff --git a/src/page/home/report/ReportHistoryItemMessage.js b/src/page/home/report/ReportHistoryItemMessage.js
deleted file mode 100644
index be2aa04f691d..000000000000
--- a/src/page/home/report/ReportHistoryItemMessage.js
+++ /dev/null
@@ -1,26 +0,0 @@
-import React from 'react';
-import PropTypes from 'prop-types';
-import _ from 'underscore';
-import ReportHistoryItemFragment from './ReportHistoryItemFragment';
-import ReportHistoryPropsTypes from './ReportHistoryPropsTypes';
-
-const propTypes = {
- // The report history item
- historyItem: PropTypes.shape(ReportHistoryPropsTypes).isRequired,
-};
-
-const ReportHistoryItemMessage = ({historyItem}) => (
- <>
- {_.map(_.compact(historyItem.message), fragment => (
-
- ))}
- >
-);
-
-ReportHistoryItemMessage.propTypes = propTypes;
-ReportHistoryItemMessage.displayName = 'ReportHistoryItemMessage';
-
-export default ReportHistoryItemMessage;
diff --git a/src/page/home/report/ReportHistoryItemSingle.js b/src/page/home/report/ReportHistoryItemSingle.js
deleted file mode 100644
index 44538c468c20..000000000000
--- a/src/page/home/report/ReportHistoryItemSingle.js
+++ /dev/null
@@ -1,55 +0,0 @@
-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 styles from '../../../style/StyleSheet';
-import CONST from '../../../CONST';
-import ReportHistoryItemDate from './ReportHistoryItemDate';
-
-const propTypes = {
- // All the data of the history item
- historyItem: PropTypes.shape(ReportHistoryPropsTypes).isRequired,
-};
-
-class ReportHistoryItemSingle extends React.PureComponent {
- render() {
- const {historyItem} = this.props;
- const avatarUrl = historyItem.automatic
- ? `${CONST.CLOUDFRONT_URL}/images/icons/concierge_2019.svg`
- : historyItem.avatar;
- return (
-
-
-
-
-
-
-
-
- {historyItem.person.map(fragment => (
-
-
-
- ))}
-
-
-
-
-
-
-
-
-
- );
- }
-}
-
-ReportHistoryItemSingle.propTypes = propTypes;
-
-export default ReportHistoryItemSingle;
diff --git a/src/page/home/report/ReportView.js b/src/page/home/report/ReportView.js
index 6c9aa2bd5728..6478ca8244d5 100644
--- a/src/page/home/report/ReportView.js
+++ b/src/page/home/report/ReportView.js
@@ -1,9 +1,9 @@
import React from 'react';
import {View} from 'react-native';
import PropTypes from 'prop-types';
-import ReportHistoryView from './ReportHistoryView';
-import ReportHistoryCompose from './ReportHistoryCompose';
-import {addHistoryItem} from '../../../lib/actions/Report';
+import ReportActionView from './ReportActionsView';
+import ReportActionCompose from './ReportActionCompose';
+import {addAction} from '../../../lib/actions/Report';
import KeyboardSpacer from '../../../components/KeyboardSpacer';
import styles from '../../../style/StyleSheet';
@@ -24,12 +24,11 @@ class ReportView extends React.PureComponent {
const shouldShowComposeForm = this.props.isActiveReport;
return (
-
+
{shouldShowComposeForm && (
- 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();
}
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/page/home/sidebar/SidebarLink.js b/src/page/home/sidebar/SidebarLink.js
index ade838375d66..46b44dcf35ed 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,6 +31,7 @@ const propTypes = {
const defaultProps = {
isUnread: false,
+ reportName: '',
};
const SidebarLink = (props) => {
diff --git a/src/page/home/sidebar/SidebarLinks.js b/src/page/home/sidebar/SidebarLinks.js
index a9d8af337f58..1af2ef40dd67 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 && (