Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[No QA] Create createIOUReportAction #10696

Merged
merged 11 commits into from
Aug 31, 2022
3 changes: 3 additions & 0 deletions src/CONST.js
Original file line number Diff line number Diff line change
Expand Up @@ -640,6 +640,9 @@ const CONST = {
SPLIT: 'split',
REQUEST: 'request',
},
REPORT_ACTION_TYPE: {
PAY: 'pay',
},
AMOUNT_MAX_LENGTH: 10,
},

Expand Down
18 changes: 17 additions & 1 deletion src/libs/NumberUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,23 @@ function rand64() {
return left + middleString + rightString;
}

/**
* @returns {Number}
*/
function generateReportActionSequenceNumber() {
// 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
// sequenceNumber if it is created before this one. We use a combination of current epoch timestamp (milliseconds)
// and a random number so that the probability of someone else having the same optimisticReportActionID is
// extremely low even if they left the comment at the same moment as another user on the same report. The random
// number is 3 digits because if we go any higher JS will convert the digits after the 16th position to 0's in
// optimisticReportActionID.
const randomNumber = Math.floor((Math.random() * (999 - 100)) + 100);
return parseInt(`${Date.now()}${randomNumber}`, 10);
}

export {
// eslint-disable-next-line import/prefer-default-export
rand64,
generateReportActionSequenceNumber,
};
84 changes: 84 additions & 0 deletions src/libs/ReportUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import _ from 'underscore';
import Str from 'expensify-common/lib/str';
import lodashGet from 'lodash/get';
import Onyx from 'react-native-onyx';
import moment from 'moment';
import ONYXKEYS from '../ONYXKEYS';
import CONST from '../CONST';
import * as Localize from './Localize';
Expand All @@ -10,6 +11,7 @@ import * as Expensicons from '../components/Icon/Expensicons';
import md5 from './md5';
import Navigation from './Navigation/Navigation';
import ROUTES from '../ROUTES';
import * as NumberUtils from './NumberUtils';

let sessionEmail;
Onyx.connect({
Expand All @@ -28,6 +30,27 @@ Onyx.connect({
},
});

let currentUserEmail;
let currentUserAccountID;
Onyx.connect({
key: ONYXKEYS.SESSION,
callback: (val) => {
// When signed out, val is undefined
if (!val) {
return;
}

currentUserEmail = val.email;
currentUserAccountID = val.accountID;
},
});

let currentUserPersonalDetails;
Onyx.connect({
key: ONYXKEYS.PERSONAL_DETAILS,
callback: val => currentUserPersonalDetails = lodashGet(val, currentUserEmail),
});

/**
* Returns the concatenated title for the PrimaryLogins of a report
*
Expand Down Expand Up @@ -556,6 +579,66 @@ function hasReportNameError(report) {
return !_.isEmpty(lodashGet(report, 'errorFields.reportName', {}));
}

/**
* Builds an optimistic IOU reportAction object
*
* @param {String} type - IOUReportAction type. Can be oneOf(create, decline, cancel, pay).
* @param {Number} amount - IOU amount in cents.
* @param {String} comment - User comment for the IOU.
* @param {String} paymentType - Only required if the IOUReportAction type is 'pay'. Can be oneOf(elsewhere, payPal, Expensify).
* @param {String} existingIOUTransactionID - Only required if the IOUReportAction type is oneOf(cancel, decline). Generates a randomID as default.
* @param {Number} existingIOUReportID - Only required if the IOUReportActions type is oneOf(decline, cancel, pay). Generates a randomID as default.
*
* @returns {Object}
*/
function buildOptimisticIOUReportAction(type, amount, comment, paymentType = '', existingIOUTransactionID = '', existingIOUReportID = 0) {
const currency = lodashGet(currentUserPersonalDetails, 'localCurrencyCode');
const IOUTransactionID = existingIOUTransactionID || NumberUtils.rand64();
const IOUReportID = existingIOUReportID || generateReportID();
const sequenceNumber = NumberUtils.generateReportActionSequenceNumber();
const originalMessage = {
amount,
comment,
currency,
IOUTransactionID,
IOUReportID,
type,
};

// We store amount, comment, currency in IOUDetails when type = pay
if (type === CONST.IOU.REPORT_ACTION_TYPE.PAY) {
_.each(['amount', 'comment', 'currency'], (key) => {
delete originalMessage[key];
});
originalMessage.IOUDetails = {amount, comment, currency};
originalMessage.paymentType = paymentType;
}

return {
actionName: CONST.REPORT.ACTIONS.TYPE.IOU,
actorAccountID: currentUserAccountID,
actorEmail: currentUserEmail,
automatic: false,
avatar: lodashGet(currentUserPersonalDetails, 'avatar', getDefaultAvatar(currentUserEmail)),

// For now, the clientID and sequenceNumber are the same.
// We are changing that as we roll out the optimistiReportAction IDs and related refactors.
clientID: sequenceNumber,
isAttachment: false,
originalMessage,
person: [{
style: 'strong',
text: lodashGet(currentUserPersonalDetails, 'displayName', currentUserEmail),
type: 'TEXT',
}],
reportActionID: NumberUtils.rand64(),
sequenceNumber,
shouldShow: true,
timestamp: moment().unix(),
pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD,
};
}

export {
getReportParticipantsTitle,
isReportMessageAttachment,
Expand Down Expand Up @@ -586,4 +669,5 @@ export {
navigateToDetailsPage,
generateReportID,
hasReportNameError,
buildOptimisticIOUReportAction,
};
15 changes: 3 additions & 12 deletions src/libs/actions/Report.js
Original file line number Diff line number Diff line change
Expand Up @@ -721,16 +721,7 @@ function buildOptimisticReportAction(reportID, text, file) {
const textForNewComment = isAttachment ? '[Attachment]'
: parser.htmlToText(htmlForNewComment);

// 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
// sequenceNumber if it is created before this one. We use a combination of current epoch timestamp (milliseconds)
// and a random number so that the probability of someone else having the same optimisticReportActionID is
// extremely low even if they left the comment at the same moment as another user on the same report. The random
// number is 3 digits because if we go any higher JS will convert the digits after the 16th position to 0's in
// optimisticReportActionID.
const randomNumber = Math.floor((Math.random() * (999 - 100)) + 100);
const optimisticReportActionID = parseInt(`${Date.now()}${randomNumber}`, 10);
const optimisticReportActionSequenceNumber = NumberUtils.generateReportActionSequenceNumber();

return {
commentText,
Expand All @@ -749,8 +740,8 @@ function buildOptimisticReportAction(reportID, text, file) {
automatic: false,

// Use the client generated ID as a optimistic action ID so we can remove it later
sequenceNumber: optimisticReportActionID,
clientID: optimisticReportActionID,
sequenceNumber: optimisticReportActionSequenceNumber,
clientID: optimisticReportActionSequenceNumber,
avatar: lodashGet(personalDetails, [currentUserEmail, 'avatar'], ReportUtils.getDefaultAvatar(currentUserEmail)),
timestamp: moment().unix(),
message: [
Expand Down