Skip to content

Commit

Permalink
Merge pull request #9556 from Expensify/marcaaron-addAttachment
Browse files Browse the repository at this point in the history
Report_AddComment Pt. 3 - `AddAttachment`
  • Loading branch information
marcaaron authored Jul 13, 2022
2 parents 0150b3e + 49a93eb commit 7768635
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 71 deletions.
160 changes: 117 additions & 43 deletions src/libs/actions/Report.js
Original file line number Diff line number Diff line change
Expand Up @@ -821,39 +821,24 @@ function fetchAllReports(
}

/**
* Add an action item to a report
*
* @param {Number} reportID
* @param {String} text
* @param {String} [text]
* @param {File} [file]
* @returns {Object}
*/
function addComment(reportID, text, file) {
function buildOptimisticReportAction(reportID, text, file) {
// For comments shorter than 10k chars, convert the comment from MD into HTML because that's how it is stored in the database
// For longer comments, skip parsing and display plaintext for performance reasons. It takes over 40s to parse a 100k long string!!
const parser = new ExpensiMark();
const commentText = text.length < 10000 ? parser.replace(text) : text;
const isAttachment = _.isEmpty(text) && file !== undefined;
const attachmentInfo = isAttachment ? file : {};

// The new sequence number will be one higher than the highest
const highestSequenceNumber = getMaxSequenceNumber(reportID);
const newSequenceNumber = highestSequenceNumber + 1;
const htmlForNewComment = isAttachment ? 'Uploading Attachment...' : commentText;

// Remove HTML from text when applying optimistic offline comment
const textForNewComment = isAttachment ? '[Attachment]'
: parser.htmlToText(htmlForNewComment);

// Update the report in Onyx to have the new sequence number
const optimisticReport = {
maxSequenceNumber: newSequenceNumber,
lastMessageTimestamp: moment().unix(),
lastMessageText: ReportUtils.formatReportLastMessageText(textForNewComment),
lastActorEmail: currentUserEmail,
unreadActionCount: 0,
lastReadSequenceNumber: newSequenceNumber,
};

// Generate a clientID so we can save the optimistic action to storage with the clientID as key. Later, we will
// remove the optimistic action when we add the real action created in the server. We do this because it's not
// safe to assume that this will use the very next sequenceNumber. An action created by another can overwrite that
Expand All @@ -865,14 +850,9 @@ function addComment(reportID, text, file) {
const randomNumber = Math.floor((Math.random() * (999 - 100)) + 100);
const optimisticReportActionID = parseInt(`${Date.now()}${randomNumber}`, 10);

// Store the optimistic action ID on the report the comment was added to. It will be removed later when refetching
// report actions in order to clear out any stuck actions (i.e. actions where the client never received a Pusher
// event, for whatever reason, from the server with the new action data
optimisticReport.optimisticReportActionIDs = [...(optimisticReportActionIDs[reportID] || []), optimisticReportActionID];

// Optimistically add the new comment to the store before waiting to save it to the server
const optimisticReportActions = {
[optimisticReportActionID]: {
return {
commentText,
reportAction: {
actionName: CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT,
actorEmail: currentUserEmail,
actorAccountID: currentUserAccountID,
Expand Down Expand Up @@ -904,12 +884,85 @@ function addComment(reportID, text, file) {
shouldShow: true,
},
};
}

/**
* Add up to two report actions to a report. This method can be called for the following situations:
*
* - Adding one comment
* - Adding one attachment
* - Add both a comment and attachment simultaneously
*
* @param {Number} reportID
* @param {String} [text]
* @param {Object} [file]
*/
function addActions(reportID, text = '', file) {
let reportCommentText = '';
let reportCommentAction;
let attachmentAction;
let commandName = 'AddComment';

if (text) {
const reportComment = buildOptimisticReportAction(reportID, text);
reportCommentAction = reportComment.reportAction;
reportCommentText = reportComment.commentText;
}

if (file) {
// When we are adding an attachment we will call AddAttachment.
// It supports sending an attachment with an optional comment and AddComment supports adding a single text comment only.
commandName = 'AddAttachment';
const attachment = buildOptimisticReportAction(reportID, '', file);
attachmentAction = attachment.reportAction;
}

// Always prefer the file as the last action over text
const lastAction = attachmentAction || reportCommentAction;

// We need a newSequenceNumber that is n larger than the current depending on how many actions we are adding.
const actionCount = text && file ? 2 : 1;
const highestSequenceNumber = getMaxSequenceNumber(reportID);
const newSequenceNumber = highestSequenceNumber + actionCount;

// Update the report in Onyx to have the new sequence number
const optimisticReport = {
maxSequenceNumber: newSequenceNumber,
lastMessageTimestamp: Date.now(),
lastMessageText: ReportUtils.formatReportLastMessageText(lastAction.message[0].text),
lastActorEmail: currentUserEmail,
unreadActionCount: 0,
lastReadSequenceNumber: newSequenceNumber,
};

// Store the optimistic action ID on the report the comment was added to. It will be removed later when refetching
// report actions in order to clear out any stuck actions (i.e. actions where the client never received a Pusher
// event, for whatever reason, from the server with the new action data
optimisticReport.optimisticReportActionIDs = [...(optimisticReportActionIDs[reportID] || [])];

if (text) {
optimisticReport.optimisticReportActionIDs.push(reportCommentAction.clientID);
}

if (file) {
optimisticReport.optimisticReportActionIDs.push(attachmentAction.clientID);
}

// Optimistically add the new actions to the store before waiting to save them to the server
const optimisticReportActions = {};
if (text) {
optimisticReportActions[reportCommentAction.clientID] = reportCommentAction;
}
if (file) {
optimisticReportActions[attachmentAction.clientID] = attachmentAction;
}

const parameters = {
reportID,
reportComment: reportCommentText,
clientID: lastAction.clientID,
commentClientID: lodashGet(reportCommentAction, 'clientID', ''),
file,
reportComment: commentText,
clientID: optimisticReportActionID,
};

const optimisticData = [
Expand Down Expand Up @@ -942,28 +995,49 @@ function addComment(reportID, text, file) {
DateUtils.setTimezoneUpdated();
}

API.write(isAttachment ? 'AddAttachment' : 'AddComment', parameters, {
const failureDataReportActions = {};
const defaultLoadingState = {
isLoading: false,
};

if (text) {
failureDataReportActions[reportCommentAction.clientID] = defaultLoadingState;
}

if (file) {
failureDataReportActions[attachmentAction.clientID] = defaultLoadingState;
}

API.write(commandName, parameters, {
optimisticData,
failureData: [
{
onyxMethod: CONST.ONYX.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`,
value: {
[optimisticReportActionID]: {
isLoading: false,
},
},
},
],
failureData: [{
onyxMethod: CONST.ONYX.METHOD.MERGE,
key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`,
value: failureDataReportActions,
}],
});
}

/**
*
* Add an attachment and optional comment.
*
* @param {Number} reportID
* @param {Object} file
* @param {File} file
* @param {String} [text]
*/
function addAttachment(reportID, file, text = '') {
addActions(reportID, text, file);
}

/**
* Add a single comment to a report
*
* @param {Number} reportID
* @param {String} text
*/
function addAttachment(reportID, file) {
addComment(reportID, '', file);
function addComment(reportID, text) {
addActions(reportID, text);
}

/**
Expand Down
16 changes: 0 additions & 16 deletions src/libs/deprecatedAPI.js
Original file line number Diff line number Diff line change
Expand Up @@ -290,21 +290,6 @@ function RejectTransaction(parameters) {
return Network.post(commandName, parameters);
}

/**
* @param {Object} parameters
* @param {String} parameters.reportComment
* @param {Number} parameters.reportID
* @param {String} parameters.clientID
* @param {File|Object} [parameters.file]
* @returns {Promise}
*/
function Report_AddComment(parameters) {
const commandName = 'Report_AddComment';
requireParameters(['reportComment', 'reportID', 'clientID'],
parameters, commandName);
return Network.post(commandName, parameters);
}

/**
* @param {Object} parameters
* @param {Number} parameters.reportID
Expand Down Expand Up @@ -873,7 +858,6 @@ export {
Plaid_GetLinkToken,
Policy_Employees_Merge,
RejectTransaction,
Report_AddComment,
Report_GetHistory,
Report_EditComment,
ResendValidateCode,
Expand Down
36 changes: 24 additions & 12 deletions src/pages/home/report/ReportActionCompose.js
Original file line number Diff line number Diff line change
Expand Up @@ -429,23 +429,16 @@ class ReportActionCompose extends React.Component {
}

/**
* Add a new comment to this chat
*
* @param {SyntheticEvent} [e]
* @returns {String}
*/
submitForm(e) {
if (e) {
e.preventDefault();
}

prepareCommentAndResetComposer() {
const trimmedComment = this.comment.trim();

// Don't submit empty comments or comments that exceed the character limit
if (this.state.isCommentEmpty || trimmedComment.length > CONST.MAX_COMMENT_LENGTH) {
return;
return '';
}

this.props.onSubmit(trimmedComment);
this.updateComment('');
this.setTextInputShouldClear(true);
if (this.props.isComposerFullSize) {
Expand All @@ -455,6 +448,25 @@ class ReportActionCompose extends React.Component {

// Important to reset the selection on Submit action
this.textInput.setNativeProps({selection: {start: 0, end: 0}});
return trimmedComment;
}

/**
* Add a new comment to this chat
*
* @param {SyntheticEvent} [e]
*/
submitForm(e) {
if (e) {
e.preventDefault();
}

const comment = this.prepareCommentAndResetComposer();
if (!comment) {
return;
}

this.props.onSubmit(comment);
}

render() {
Expand Down Expand Up @@ -492,8 +504,8 @@ class ReportActionCompose extends React.Component {
<AttachmentModal
headerTitle={this.props.translate('reportActionCompose.sendAttachment')}
onConfirm={(file) => {
this.submitForm();
Report.addAttachment(this.props.reportID, file);
const comment = this.prepareCommentAndResetComposer();
Report.addAttachment(this.props.reportID, file, comment);
this.setTextInputShouldClear(false);
}}
>
Expand Down

0 comments on commit 7768635

Please sign in to comment.