diff --git a/src/components/ReportTransaction.js b/src/components/ReportTransaction.js index 89252b88dfb4..be53b11c742e 100644 --- a/src/components/ReportTransaction.js +++ b/src/components/ReportTransaction.js @@ -61,9 +61,9 @@ class ReportTransaction extends Component { ReportActions.clearSendMoneyErrors(this.props.chatReportID, this.props.action.reportActionID); } if (this.props.action.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD) { - ReportActions.deleteOptimisticReportAction(this.props.chatReportID, this.props.action.sequenceNumber); + ReportActions.deleteOptimisticReportAction(this.props.chatReportID, this.props.action.reportActionID); } else { - ReportActions.clearReportActionErrors(this.props.chatReportID, this.props.action.sequenceNumber); + ReportActions.clearReportActionErrors(this.props.chatReportID, this.props.action.reportActionID); } }} pendingAction={this.props.action.pendingAction} diff --git a/src/libs/E2E/apiMocks/openApp.js b/src/libs/E2E/apiMocks/openApp.js index d742823aa883..c6cbe1d743e5 100644 --- a/src/libs/E2E/apiMocks/openApp.js +++ b/src/libs/E2E/apiMocks/openApp.js @@ -1733,7 +1733,6 @@ export default () => ({ chatType: '', ownerEmail: '__fake__', policyID: '_FAKE_', - maxSequenceNumber: 2, participants: [ 'concierge@expensify.com', ], @@ -1741,7 +1740,6 @@ export default () => ({ lastVisitedTimestamp: 1671126234191, lastReadTimestamp: 1671126234191, lastReadCreated: '1980-01-01 00:00:00.000', - lastReadSequenceNumber: 2, lastActionCreated: '2022-08-03 06:45:00', lastMessageTimestamp: 1659509100000, lastMessageText: 'You can easily track, approve, and pay bills in Expensify with your custom compa', @@ -1764,7 +1762,6 @@ export default () => ({ chatType: 'policyExpenseChat', ownerEmail: 'applausetester+perf2@applause.expensifail.com', policyID: 'C28C2634DD7226B8', - maxSequenceNumber: 1, participants: [ 'applausetester@applause.expensifail.com', 'applausetester+perf2@applause.expensifail.com', @@ -1773,7 +1770,6 @@ export default () => ({ lastVisitedTimestamp: 1669129206943, lastReadTimestamp: 1669129206943, lastReadCreated: '1980-01-01 00:00:00.000', - lastReadSequenceNumber: 1, lastActionCreated: '2022-11-03 20:30:55.599', lastMessageTimestamp: 1667507455599, lastMessageText: '', @@ -1793,7 +1789,6 @@ export default () => ({ chatType: '', ownerEmail: '__fake__', policyID: '_FAKE_', - maxSequenceNumber: 8, participants: [ 'applausetester+ihchat4@applause.expensifail.com', ], @@ -1801,7 +1796,6 @@ export default () => ({ lastVisitedTimestamp: 1671205050152, lastReadTimestamp: 1671205050152, lastReadCreated: '1980-01-01 00:00:00.000', - lastReadSequenceNumber: 8, lastActionCreated: '2022-08-02 20:03:42', lastMessageTimestamp: 1659470622000, lastMessageText: 'Requested \u20b41.67 from applausetester+perf2@applause.expensifail.com', @@ -1821,7 +1815,6 @@ export default () => ({ chatType: '', ownerEmail: '__fake__', policyID: '_FAKE_', - maxSequenceNumber: 17, participants: [ 'fake3@gmail.com', ], @@ -1829,7 +1822,6 @@ export default () => ({ lastVisitedTimestamp: 1671210740419, lastReadTimestamp: 1671210740419, lastReadCreated: '1980-01-01 00:00:00.000', - lastReadSequenceNumber: 17, lastActionCreated: '2022-11-04 21:18:00.038', lastMessageTimestamp: 1667596680038, lastMessageText: 'Cancelled the \u20b440.00 request', @@ -1849,7 +1841,6 @@ export default () => ({ chatType: '', ownerEmail: '__fake__', policyID: '_FAKE_', - maxSequenceNumber: 2, participants: [ 'fake3@gmail.com', 'fake6@gmail.com', @@ -1860,7 +1851,6 @@ export default () => ({ lastVisitedTimestamp: 1671209362667, lastReadTimestamp: 1671209362667, lastReadCreated: '1980-01-01 00:00:00.000', - lastReadSequenceNumber: 2, lastActionCreated: '2022-08-01 20:48:16', lastMessageTimestamp: 1659386896000, lastMessageText: 'applausetester+perf2@applause.expensifail.com', @@ -1880,7 +1870,6 @@ export default () => ({ chatType: '', ownerEmail: '__fake__', policyID: '_FAKE_', - maxSequenceNumber: 1, participants: [ 'fake1@gmail.com', 'fake2@gmail.com', @@ -1895,7 +1884,6 @@ export default () => ({ lastVisitedTimestamp: 1671470568415, lastReadTimestamp: 1671470568415, lastReadCreated: '1980-01-01 00:00:00.000', - lastReadSequenceNumber: 1, lastActionCreated: '2022-08-01 20:49:11', lastMessageTimestamp: 1659386951000, lastMessageText: 'Say hello\ud83d\ude10', @@ -1915,7 +1903,6 @@ export default () => ({ chatType: 'policyExpenseChat', ownerEmail: 'applausetester+perf2@applause.expensifail.com', policyID: '1CE001C4B9F3CA54', - maxSequenceNumber: 2, participants: [ 'fake3@gmail.com', 'applausetester+perf2@applause.expensifail.com', @@ -1924,7 +1911,6 @@ export default () => ({ lastVisitedTimestamp: 1664363369565, lastReadTimestamp: 1664363369565, lastReadCreated: '1980-01-01 00:00:00.000', - lastReadSequenceNumber: 2, lastActionCreated: '2022-08-16 12:30:57', lastMessageTimestamp: 1660653057000, lastMessageText: '', @@ -1944,7 +1930,6 @@ export default () => ({ chatType: '', ownerEmail: '__fake__', policyID: '_FAKE_', - maxSequenceNumber: 1, participants: [ 'applausetester+ihchat4@applause.expensifail.com', 'fake6@gmail.com', @@ -1953,7 +1938,6 @@ export default () => ({ lastVisitedTimestamp: 1669197163626, lastReadTimestamp: 1669197163626, lastReadCreated: '1980-01-01 00:00:00.000', - lastReadSequenceNumber: 1, lastActionCreated: '2022-08-02 20:03:41', lastMessageTimestamp: 1659470621000, lastMessageText: 'Split \u20b45.00 with applausetester+perf2@applause.expensifail.com and applauseteste', @@ -1973,7 +1957,6 @@ export default () => ({ chatType: '', ownerEmail: '__fake__', policyID: '_FAKE_', - maxSequenceNumber: 1787, participants: [ 'fake6@gmail.com', ], @@ -1981,7 +1964,6 @@ export default () => ({ lastVisitedTimestamp: 1671214557025, lastReadTimestamp: 1671214557025, lastReadCreated: '1980-01-01 00:00:00.000', - lastReadSequenceNumber: 1787, lastActionCreated: '2022-12-09 10:17:18.362', lastMessageTimestamp: 1670581038362, lastMessageText: 'RR', @@ -2002,7 +1984,6 @@ export default () => ({ chatType: 'policyRoom', ownerEmail: '__fake__', policyID: 'C28C2634DD7226B8', - maxSequenceNumber: 3, participants: [ 'applausetester+pd1005@applause.expensifail.com', ], @@ -2010,7 +1991,6 @@ export default () => ({ lastVisitedTimestamp: 1669298874528, lastReadTimestamp: 1669298874528, lastReadCreated: '1980-01-01 00:00:00.000', - lastReadSequenceNumber: 3, lastActionCreated: '2022-10-12 17:47:45.228', lastMessageTimestamp: 1665596865228, lastMessageText: 'STAGING_CHAT_MESSAGE_A2C534B7-3509-416E-A0AD-8463831C29DD', @@ -2030,7 +2010,6 @@ export default () => ({ chatType: 'policyAnnounce', ownerEmail: '__fake__', policyID: 'A6511FF8D2EE7661', - maxSequenceNumber: 0, participants: [ 'applausetester+perf2@applause.expensifail.com', ], @@ -2038,7 +2017,6 @@ export default () => ({ lastVisitedTimestamp: 0, lastReadTimestamp: 0, lastReadCreated: '1980-01-01 00:00:00.000', - lastReadSequenceNumber: 0, lastActionCreated: '', lastMessageTimestamp: 0, lastMessageText: '', @@ -2058,7 +2036,6 @@ export default () => ({ chatType: 'policyExpenseChat', ownerEmail: 'applausetester+perf2@applause.expensifail.com', policyID: 'A6511FF8D2EE7661', - maxSequenceNumber: 0, participants: [ 'applausetester+perf2@applause.expensifail.com', ], @@ -2066,7 +2043,6 @@ export default () => ({ lastVisitedTimestamp: 1669122367932, lastReadTimestamp: 1669122367932, lastReadCreated: '1980-01-01 00:00:00.000', - lastReadSequenceNumber: 0, lastActionCreated: '', lastMessageTimestamp: 0, lastMessageText: '', @@ -2086,7 +2062,6 @@ export default () => ({ chatType: '', ownerEmail: '__fake__', policyID: '_FAKE_', - maxSequenceNumber: 3, participants: [ 'concierge@expensify.com', 'applausetester+0901abb@applause.expensifail.com', @@ -2098,7 +2073,6 @@ export default () => ({ lastVisitedTimestamp: 1671211239096, lastReadTimestamp: 1671211239096, lastReadCreated: '1980-01-01 00:00:00.000', - lastReadSequenceNumber: 3, lastActionCreated: '2022-11-03 20:48:58.815', lastMessageTimestamp: 1667508538815, lastMessageText: 'Hi there, thanks for reaching out! How may I help?', @@ -2118,7 +2092,6 @@ export default () => ({ chatType: '', ownerEmail: '__fake__', policyID: '_FAKE_', - maxSequenceNumber: 1, participants: [ 'applausetester+42222abb@applause.expensifail.com', ], @@ -2126,7 +2099,6 @@ export default () => ({ lastVisitedTimestamp: 1671213666675, lastReadTimestamp: 1671213666675, lastReadCreated: '1980-01-01 00:00:00.000', - lastReadSequenceNumber: 1, lastActionCreated: '2022-12-01 08:05:11.009', lastMessageTimestamp: 1669881911009, lastMessageText: 'Test', @@ -2146,7 +2118,6 @@ export default () => ({ chatType: '', ownerEmail: '__fake__', policyID: '_FAKE_', - maxSequenceNumber: 0, participants: [ 'fake1@gmail.com', ], @@ -2154,7 +2125,6 @@ export default () => ({ lastVisitedTimestamp: 1669300587843, lastReadTimestamp: 1669300587843, lastReadCreated: '1980-01-01 00:00:00.000', - lastReadSequenceNumber: 0, lastActionCreated: '', lastMessageTimestamp: 0, lastMessageText: '', @@ -2174,7 +2144,6 @@ export default () => ({ chatType: 'policyRoom', ownerEmail: '__fake__', policyID: 'C28C2634DD7226B8', - maxSequenceNumber: 3, participants: [ 'applausetester+pd1005@applause.expensifail.com', ], @@ -2182,7 +2151,6 @@ export default () => ({ lastVisitedTimestamp: 1669881965738, lastReadTimestamp: 1669881965738, lastReadCreated: '1980-01-01 00:00:00.000', - lastReadSequenceNumber: 3, lastActionCreated: '2022-10-12 12:20:00.668', lastMessageTimestamp: 1665577200668, lastMessageText: 'Room renamed to #jack', @@ -2202,7 +2170,6 @@ export default () => ({ chatType: '', ownerEmail: '__fake__', policyID: '_FAKE_', - maxSequenceNumber: 2, participants: [ 'christoph+hightraffic@margelo.io', ], @@ -2210,7 +2177,6 @@ export default () => ({ lastVisitedTimestamp: 1671214566347, lastReadTimestamp: 1671214566347, lastReadCreated: '1980-01-01 00:00:00.000', - lastReadSequenceNumber: 2, lastActionCreated: '2022-12-16 18:14:00.208', lastMessageTimestamp: 1671214440208, lastMessageText: 'Requested \u20ac200.00 from Christoph for Essen mit Kunden', @@ -2231,7 +2197,6 @@ export default () => ({ chatType: 'policyRoom', ownerEmail: '__fake__', policyID: 'C28C2634DD7226B8', - maxSequenceNumber: 13, participants: [ 'applausetester+pd1005@applause.expensifail.com', ], @@ -2239,7 +2204,6 @@ export default () => ({ lastVisitedTimestamp: 1670955487510, lastReadTimestamp: 1670955487510, lastReadCreated: '1980-01-01 00:00:00.000', - lastReadSequenceNumber: 13, lastActionCreated: '2022-11-29 12:38:15.985', lastMessageTimestamp: 1669725495985, lastMessageText: 'fff', @@ -2260,7 +2224,6 @@ export default () => ({ chatType: 'domainAll', ownerEmail: '+@applause.expensifail.com', policyID: '_FAKE_', - maxSequenceNumber: 17, participants: [ 'applausetester+bernardo@applause.expensifail.com', ], @@ -2268,7 +2231,6 @@ export default () => ({ lastVisitedTimestamp: 1669634659097, lastReadTimestamp: 1669634659097, lastReadCreated: '1980-01-01 00:00:00.000', - lastReadSequenceNumber: 14, lastActionCreated: '2022-11-29 21:08:00.793', lastMessageTimestamp: 1669756080793, lastMessageText: 'Iviviviv8b', @@ -2288,7 +2250,6 @@ export default () => ({ chatType: '', ownerEmail: '__fake__', policyID: '_FAKE_', - maxSequenceNumber: 0, participants: [ 'andreylazutkinutest@gmail.com', 'fake1@gmail.com', @@ -2303,7 +2264,6 @@ export default () => ({ lastVisitedTimestamp: 1669129467258, lastReadTimestamp: 1669129467258, lastReadCreated: '1980-01-01 00:00:00.000', - lastReadSequenceNumber: 0, lastActionCreated: '', lastMessageTimestamp: 0, lastMessageText: '', @@ -2323,7 +2283,6 @@ export default () => ({ chatType: 'policyAdmins', ownerEmail: '__fake__', policyID: 'A6511FF8D2EE7661', - maxSequenceNumber: 0, participants: [ 'applausetester+perf2@applause.expensifail.com', ], @@ -2331,7 +2290,6 @@ export default () => ({ lastVisitedTimestamp: 0, lastReadTimestamp: 0, lastReadCreated: '1980-01-01 00:00:00.000', - lastReadSequenceNumber: 0, lastActionCreated: '', lastMessageTimestamp: 0, lastMessageText: '', @@ -2351,7 +2309,6 @@ export default () => ({ chatType: '', ownerEmail: '__fake__', policyID: '_FAKE_', - maxSequenceNumber: 1, participants: [ 'concierge@expensify.com', 'andreylazutkinutest@gmail.com', @@ -2366,7 +2323,6 @@ export default () => ({ lastVisitedTimestamp: 1671211247254, lastReadTimestamp: 1671211247254, lastReadCreated: '1980-01-01 00:00:00.000', - lastReadSequenceNumber: 1, lastActionCreated: '2022-09-15 12:57:59.526', lastMessageTimestamp: 1663246679526, lastMessageText: "\ud83d\udc4b Welcome to Expensify! I'm Concierge. Is there anything I can help with? Click ", @@ -2387,7 +2343,6 @@ export default () => ({ chatType: '', ownerEmail: '__fake__', policyID: '_FAKE_', - maxSequenceNumber: 3, participants: [ 'concierge@expensify.com', 'andreylazutkinutest@gmail.com', @@ -2400,7 +2355,6 @@ export default () => ({ lastVisitedTimestamp: 1669634649909, lastReadTimestamp: 1669634649909, lastReadCreated: '1980-01-01 00:00:00.000', - lastReadSequenceNumber: 3, lastActionCreated: '2022-09-16 11:12:46.739', lastMessageTimestamp: 1663326766739, lastMessageText: 'Hi there! How can I help?\u00a0', @@ -2420,7 +2374,6 @@ export default () => ({ chatType: 'policyRoom', ownerEmail: '__fake__', policyID: 'C28C2634DD7226B8', - maxSequenceNumber: 1, participants: [ 'applausetester+pd1005@applause.expensifail.com', ], @@ -2428,7 +2381,6 @@ export default () => ({ lastVisitedTimestamp: 1669197883208, lastReadTimestamp: 1669197883208, lastReadCreated: '1980-01-01 00:00:00.000', - lastReadSequenceNumber: 1, lastActionCreated: '2022-10-12 12:46:43.577', lastMessageTimestamp: 1665578803577, lastMessageText: 'Room renamed to #jackd23', @@ -2448,7 +2400,6 @@ export default () => ({ chatType: '', ownerEmail: '__fake__', policyID: '_FAKE_', - maxSequenceNumber: 0, participants: [ 'fake5@gmail.com', ], @@ -2456,7 +2407,6 @@ export default () => ({ lastVisitedTimestamp: 1671205430161, lastReadTimestamp: 1671205430161, lastReadCreated: '1980-01-01 00:00:00.000', - lastReadSequenceNumber: 0, lastActionCreated: '', lastMessageTimestamp: 0, lastMessageText: '', diff --git a/src/libs/E2E/apiMocks/openReport.js b/src/libs/E2E/apiMocks/openReport.js index 22bd62fcc57d..954f6a89a295 100644 --- a/src/libs/E2E/apiMocks/openReport.js +++ b/src/libs/E2E/apiMocks/openReport.js @@ -9,7 +9,6 @@ export default () => ({ chatType: '', ownerEmail: '__fake__', policyID: '_FAKE_', - maxSequenceNumber: 1, participants: [ 'fake1@gmail.com', 'fake2@gmail.com', @@ -24,7 +23,6 @@ export default () => ({ lastVisitedTimestamp: 1671470568415, lastReadTimestamp: 1671470568415, lastReadCreated: '1980-01-01 00:00:00.000', - lastReadSequenceNumber: 1, lastActionCreated: '2022-08-01 20:49:11', lastMessageTimestamp: 1659386951000, lastMessageText: 'Say hello\ud83d\ude10', @@ -43,7 +41,7 @@ export default () => ({ onyxMethod: 'merge', key: 'reportActions_98345625', value: { - 0: { + 226245034: { reportActionID: '226245034', actionName: 'CREATED', created: '2022-08-01 20:48:58', @@ -70,11 +68,9 @@ export default () => ({ }, ], automatic: false, - sequenceNumber: 0, - clientID: '', shouldShow: true, }, - 1: { + 1082059149: { person: [ { type: 'TEXT', @@ -93,7 +89,6 @@ export default () => ({ }, ], originalMessage: { - clientID: '1659386951676734', html: 'Say hello\ud83d\ude10', }, avatar: 'https://d1wpcgnaa73g0y.cloudfront.net/301e37631eca9e3127d6b668822e3a53771551f6_128.jpeg', @@ -101,10 +96,8 @@ export default () => ({ timestamp: 1659386951, reportActionTimestamp: 1659386951000, automatic: false, - sequenceNumber: 1, actionName: 'ADDCOMMENT', shouldShow: true, - clientID: '1659386951676734', reportActionID: '1082059149', }, }, diff --git a/src/libs/NumberUtils.js b/src/libs/NumberUtils.js index f2117a36980f..ef2ac88aaff1 100644 --- a/src/libs/NumberUtils.js +++ b/src/libs/NumberUtils.js @@ -35,23 +35,7 @@ function rand64() { return left + middleString + rightString; } -/** - * @returns {Number} - */ -function generateReportActionClientID() { - // 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 clientID 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 - // clientID. - 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, - generateReportActionClientID, }; diff --git a/src/libs/ReportUtils.js b/src/libs/ReportUtils.js index 70dadec83653..51471aafe8c7 100644 --- a/src/libs/ReportUtils.js +++ b/src/libs/ReportUtils.js @@ -638,15 +638,11 @@ function hasReportNameError(report) { } /** - * @param {Number} sequenceNumber sequenceNumber must be provided and it must be a number. It cannot and should not be a clientID, - * reportActionID, or anything else besides an estimate of what the next sequenceNumber will be for the - * optimistic report action. Until we deprecate sequenceNumbers please assume that all report actions - * have them and they should be numbers. * @param {String} [text] * @param {File} [file] * @returns {Object} */ -function buildOptimisticReportAction(sequenceNumber, text, file) { +function buildOptimisticAddCommentReportAction(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(); @@ -674,8 +670,6 @@ function buildOptimisticReportAction(sequenceNumber, text, file) { }, ], automatic: false, - sequenceNumber, - clientID: NumberUtils.generateReportActionClientID(), avatar: lodashGet(allPersonalDetails, [currentUserEmail, 'avatar'], getDefaultAvatar(currentUserEmail)), created: DateUtils.getDBTime(), message: [ @@ -797,20 +791,19 @@ function getIOUReportActionMessage(type, total, participants, comment, currency, /** * Builds an optimistic IOU reportAction object * - * @param {Number} sequenceNumber - Caller is responsible for providing a best guess at what the next sequenceNumber will be. * @param {String} type - IOUReportAction type. Can be oneOf(create, decline, cancel, pay, split). * @param {Number} amount - IOU amount in cents. * @param {String} currency * @param {String} comment - User comment for the IOU. * @param {Array} participants - An array with participants details. - * @param {String} paymentType - Only required if the IOUReportAction type is 'pay'. Can be oneOf(elsewhere, payPal, Expensify). - * @param {String} iouTransactionID - Only required if the IOUReportAction type is oneOf(cancel, decline). Generates a randomID as default. - * @param {String} iouReportID - Only required if the IOUReportActions type is oneOf(decline, cancel, pay). Generates a randomID as default. - * @param {String} isSettlingUp - Whether we are settling up an IOU. + * @param {String} [paymentType] - Only required if the IOUReportAction type is 'pay'. Can be oneOf(elsewhere, payPal, Expensify). + * @param {String} [iouTransactionID] - Only required if the IOUReportAction type is oneOf(cancel, decline). Generates a randomID as default. + * @param {String} [iouReportID] - Only required if the IOUReportActions type is oneOf(decline, cancel, pay). Generates a randomID as default. + * @param {Boolean} [isSettlingUp] - Whether we are settling up an IOU. * * @returns {Object} */ -function buildOptimisticIOUReportAction(sequenceNumber, type, amount, currency, comment, participants, paymentType = '', iouTransactionID = '', iouReportID = '', isSettlingUp) { +function buildOptimisticIOUReportAction(type, amount, currency, comment, participants, paymentType = '', iouTransactionID = '', iouReportID = '', isSettlingUp = false) { const IOUTransactionID = iouTransactionID || NumberUtils.rand64(); const IOUReportID = iouReportID || generateReportID(); const originalMessage = { @@ -842,7 +835,6 @@ function buildOptimisticIOUReportAction(sequenceNumber, type, amount, currency, actorEmail: currentUserEmail, automatic: false, avatar: lodashGet(currentUserPersonalDetails, 'avatar', getDefaultAvatar(currentUserEmail)), - clientID: NumberUtils.generateReportActionClientID(), isAttachment: false, originalMessage, message: getIOUReportActionMessage(type, amount, participants, comment, currency, paymentType, isSettlingUp), @@ -852,7 +844,6 @@ function buildOptimisticIOUReportAction(sequenceNumber, type, amount, currency, type: 'TEXT', }], reportActionID: NumberUtils.rand64(), - sequenceNumber, shouldShow: true, created: DateUtils.getDBTime(), pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, @@ -893,10 +884,8 @@ function buildOptimisticChatReport( lastActorEmail: '', lastMessageHtml: '', lastMessageText: null, - lastReadSequenceNumber: 0, lastReadTime: currentTime, lastActionCreated: currentTime, - maxSequenceNumber: 0, notificationPreference, oldPolicyName, ownerEmail, @@ -917,55 +906,50 @@ function buildOptimisticChatReport( */ function buildOptimisticCreatedReportAction(ownerEmail) { return { - 0: { - actionName: CONST.REPORT.ACTIONS.TYPE.CREATED, - reportActionID: NumberUtils.rand64(), - pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, - actorAccountID: currentUserAccountID, - message: [ - { - type: CONST.REPORT.MESSAGE.TYPE.TEXT, - style: 'strong', - text: ownerEmail === currentUserEmail ? 'You' : ownerEmail, - }, - { - type: CONST.REPORT.MESSAGE.TYPE.TEXT, - style: 'normal', - text: ' created this report', - }, - ], - person: [ - { - type: CONST.REPORT.MESSAGE.TYPE.TEXT, - style: 'strong', - text: lodashGet(allPersonalDetails, [currentUserEmail, 'displayName'], currentUserEmail), - }, - ], - automatic: false, - sequenceNumber: 0, - avatar: lodashGet(allPersonalDetails, [currentUserEmail, 'avatar'], getDefaultAvatar(currentUserEmail)), - created: DateUtils.getDBTime(), - shouldShow: true, - }, + reportActionID: NumberUtils.rand64(), + actionName: CONST.REPORT.ACTIONS.TYPE.CREATED, + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + actorAccountID: currentUserAccountID, + message: [ + { + type: CONST.REPORT.MESSAGE.TYPE.TEXT, + style: 'strong', + text: ownerEmail === currentUserEmail ? 'You' : ownerEmail, + }, + { + type: CONST.REPORT.MESSAGE.TYPE.TEXT, + style: 'normal', + text: ' created this report', + }, + ], + person: [ + { + type: CONST.REPORT.MESSAGE.TYPE.TEXT, + style: 'strong', + text: lodashGet(allPersonalDetails, [currentUserEmail, 'displayName'], currentUserEmail), + }, + ], + automatic: false, + avatar: lodashGet(allPersonalDetails, [currentUserEmail, 'avatar'], getDefaultAvatar(currentUserEmail)), + created: DateUtils.getDBTime(), + shouldShow: true, }; } /** * Returns the necessary reportAction onyx data to indicate that a chat has been archived * - * @param {Number} sequenceNumber * @param {String} ownerEmail * @param {String} policyName * @param {String} reason - A reason why the chat has been archived * @returns {Object} */ -function buildOptimisticClosedReportAction(sequenceNumber, ownerEmail, policyName, reason = CONST.REPORT.ARCHIVE_REASON.DEFAULT) { +function buildOptimisticClosedReportAction(ownerEmail, policyName, reason = CONST.REPORT.ARCHIVE_REASON.DEFAULT) { return { actionName: CONST.REPORT.ACTIONS.TYPE.CLOSED, actorAccountID: currentUserAccountID, automatic: false, avatar: lodashGet(allPersonalDetails, [currentUserEmail, 'avatar'], getDefaultAvatar(currentUserEmail)), - clientID: NumberUtils.generateReportActionClientID(), created: DateUtils.getDBTime(), message: [ { @@ -992,7 +976,6 @@ function buildOptimisticClosedReportAction(sequenceNumber, ownerEmail, policyNam }, ], reportActionID: NumberUtils.rand64(), - sequenceNumber, shouldShow: true, }; } @@ -1017,8 +1000,10 @@ function buildOptimisticWorkspaceChats(policyID, policyName) { CONST.REPORT.NOTIFICATION_PREFERENCE.DAILY, ); const announceChatReportID = announceChatData.reportID; - const announceReportActionData = buildOptimisticCreatedReportAction(announceChatData.ownerEmail); - const announceCreatedReportActionID = announceReportActionData[0].reportActionID; + const announceCreatedAction = buildOptimisticCreatedReportAction(announceChatData.ownerEmail); + const announceReportActionData = { + [announceCreatedAction.reportActionID]: announceCreatedAction, + }; const adminsChatData = buildOptimisticChatReport( [currentUserEmail], @@ -1030,8 +1015,10 @@ function buildOptimisticWorkspaceChats(policyID, policyName) { policyName, ); const adminsChatReportID = adminsChatData.reportID; - const adminsReportActionData = buildOptimisticCreatedReportAction(adminsChatData.ownerEmail); - const adminsCreatedReportActionID = adminsReportActionData[0].reportActionID; + const adminsCreatedAction = buildOptimisticCreatedReportAction(adminsChatData.ownerEmail); + const adminsReportActionData = { + [adminsCreatedAction.reportActionID]: adminsCreatedAction, + }; const expenseChatData = buildOptimisticChatReport( [currentUserEmail], @@ -1043,22 +1030,24 @@ function buildOptimisticWorkspaceChats(policyID, policyName) { policyName, ); const expenseChatReportID = expenseChatData.reportID; - const expenseReportActionData = buildOptimisticCreatedReportAction(expenseChatData.ownerEmail); - const expenseCreatedReportActionID = expenseReportActionData[0].reportActionID; + const expenseReportCreatedAction = buildOptimisticCreatedReportAction(expenseChatData.ownerEmail); + const expenseReportActionData = { + [expenseReportCreatedAction.reportActionID]: expenseReportCreatedAction, + }; return { announceChatReportID, announceChatData, announceReportActionData, - announceCreatedReportActionID, + announceCreatedReportActionID: announceCreatedAction.reportActionID, adminsChatReportID, adminsChatData, adminsReportActionData, - adminsCreatedReportActionID, + adminsCreatedReportActionID: adminsCreatedAction.reportActionID, expenseChatReportID, expenseChatData, expenseReportActionData, - expenseCreatedReportActionID, + expenseCreatedReportActionID: expenseReportCreatedAction.reportActionID, }; } @@ -1309,7 +1298,7 @@ export { buildOptimisticCreatedReportAction, buildOptimisticIOUReport, buildOptimisticIOUReportAction, - buildOptimisticReportAction, + buildOptimisticAddCommentReportAction, shouldReportBeInOptionList, getChatByParticipants, getAllPolicyReports, diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index 08907e573426..63ed71b9cc2c 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -5,7 +5,6 @@ import Str from 'expensify-common/lib/str'; import CONST from '../../CONST'; import ONYXKEYS from '../../ONYXKEYS'; import ROUTES from '../../ROUTES'; -import * as Report from './Report'; import Navigation from '../Navigation/Navigation'; import * as Localize from '../Localize'; import asyncOpenURL from '../asyncOpenURL'; @@ -75,10 +74,10 @@ function requestMoney(report, amount, currency, recipientEmail, participant, com } else { iouReport = ReportUtils.buildOptimisticIOUReport(recipientEmail, debtorEmail, amount, chatReport.reportID, currency, preferredLocale); } - const newSequenceNumber = Report.getMaxSequenceNumber(chatReport.reportID) + 1; + // Note: The created action must be optimistically generated before the IOU action so there's no chance that the created action appears after the IOU action in the chat + const optimisticCreatedAction = ReportUtils.buildOptimisticCreatedReportAction(recipientEmail); const optimisticReportAction = ReportUtils.buildOptimisticIOUReportAction( - newSequenceNumber, CONST.IOU.REPORT_ACTION_TYPE.CREATE, amount, currency, @@ -90,127 +89,110 @@ function requestMoney(report, amount, currency, recipientEmail, participant, com ); // First, add data that will be used in all cases - const optimisticData = [ - { - onyxMethod: CONST.ONYX.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, - value: { - ...chatReport, - lastReadTime: DateUtils.getDBTime(), - lastReadSequenceNumber: newSequenceNumber, - maxSequenceNumber: newSequenceNumber, - lastMessageText: optimisticReportAction.message[0].text, - lastMessageHtml: optimisticReportAction.message[0].html, - hasOutstandingIOU: iouReport.total !== 0, - iouReportID: iouReport.reportID, - }, - }, - { - onyxMethod: CONST.ONYX.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.reportID}`, - value: { - [optimisticReportAction.sequenceNumber]: optimisticReportAction, - }, + const optimisticChatReportData = { + onyxMethod: CONST.ONYX.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, + value: { + ...chatReport, + lastReadTime: DateUtils.getDBTime(), + lastMessageText: optimisticReportAction.message[0].text, + lastMessageHtml: optimisticReportAction.message[0].html, + hasOutstandingIOU: iouReport.total !== 0, + iouReportID: iouReport.reportID, }, - { - onyxMethod: chatReport.hasOutstandingIOU ? CONST.ONYX.METHOD.MERGE : CONST.ONYX.METHOD.SET, - key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`, - value: iouReport, + }; + + const optimisticIOUReportData = { + onyxMethod: chatReport.hasOutstandingIOU ? CONST.ONYX.METHOD.MERGE : CONST.ONYX.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`, + value: iouReport, + }; + + const optimisticReportActionsData = { + onyxMethod: CONST.ONYX.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.reportID}`, + value: { + [optimisticReportAction.reportActionID]: optimisticReportAction, }, - ]; + }; - const successData = [ - { - onyxMethod: CONST.ONYX.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.reportID}`, - value: { - [optimisticReportAction.sequenceNumber]: { - pendingAction: null, - }, + let chatReportSuccessData = {}; + const reportActionsSuccessData = { + onyxMethod: CONST.ONYX.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.reportID}`, + value: { + [optimisticReportAction.reportActionID]: { + pendingAction: null, }, }, - ]; - const failureData = [ - { - onyxMethod: CONST.ONYX.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, - value: { - hasOutstandingIOU: chatReport.hasOutstandingIOU, - }, + }; + + const chatReportFailureData = { + onyxMethod: CONST.ONYX.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, + value: { + hasOutstandingIOU: chatReport.hasOutstandingIOU, }, - { - onyxMethod: CONST.ONYX.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.reportID}`, - value: { - [optimisticReportAction.sequenceNumber]: { - ...optimisticReportAction, - pendingAction: null, - errors: { - [DateUtils.getMicroseconds()]: Localize.translateLocal('iou.error.genericCreateFailureMessage'), - }, + }; + + const reportActionsFailureData = { + onyxMethod: CONST.ONYX.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.reportID}`, + value: { + [optimisticReportAction.reportActionID]: { + ...optimisticReportAction, + pendingAction: null, + errors: { + [DateUtils.getMicroseconds()]: Localize.translateLocal('iou.error.genericCreateFailureMessage'), }, }, }, - ]; - - let optimisticCreatedAction; + }; // Now, let's add the data we need just when we are creating a new chat report if (isNewChat) { - optimisticCreatedAction = ReportUtils.buildOptimisticCreatedReportAction(recipientEmail); - // Change the method to set for new reports because it doesn't exist yet, is faster, // and we need the data to be available when we navigate to the chat page - optimisticData[0].onyxMethod = CONST.ONYX.METHOD.SET; - optimisticData[0].value = { - ...optimisticData[0].value, - pendingFields: {createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD}, - }; - optimisticData[1].onyxMethod = CONST.ONYX.METHOD.SET; - optimisticData[1].value = { - ...optimisticCreatedAction, - ...optimisticData[1].value, - }; - optimisticData[2].onyxMethod = CONST.ONYX.METHOD.SET; - successData.push( - { - onyxMethod: CONST.ONYX.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, - value: { - pendingFields: null, - errorFields: null, - }, - }, - { - onyxMethod: CONST.ONYX.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.reportID}`, - value: { - 0: { - pendingAction: null, - }, - }, - }, - ); + optimisticChatReportData.onyxMethod = CONST.ONYX.METHOD.SET; + optimisticIOUReportData.onyxMethod = CONST.ONYX.METHOD.SET; + optimisticReportActionsData.onyxMethod = CONST.ONYX.METHOD.SET; - failureData.push( - { - onyxMethod: CONST.ONYX.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, - value: { - pendingFields: null, - }, - }, - { - onyxMethod: CONST.ONYX.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.reportID}`, - value: { - 0: { - pendingAction: null, - }, - }, + // Then add and clear pending fields from the chat report + optimisticChatReportData.value.pendingFields = {createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD}; + chatReportSuccessData = { + onyxMethod: CONST.ONYX.METHOD.MERGE, + key: optimisticChatReportData.key, + value: { + pendingFields: null, + errorFields: null, }, - ); + }; + chatReportFailureData.value.pendingFields = null; + + // Then add an optimistic created action + optimisticReportActionsData.value[optimisticCreatedAction.reportActionID] = optimisticCreatedAction; + reportActionsSuccessData.value[optimisticCreatedAction.reportActionID] = {pendingAction: null}; + reportActionsFailureData.value[optimisticCreatedAction.reportActionID] = {pendingAction: null}; + } + + const optimisticData = [ + optimisticChatReportData, + optimisticIOUReportData, + optimisticReportActionsData, + ]; + + const successData = [ + reportActionsSuccessData, + ]; + if (!_.isEmpty(chatReportSuccessData)) { + successData.push(chatReportSuccessData); } + + const failureData = [ + chatReportFailureData, + reportActionsFailureData, + ]; + API.write('RequestMoney', { debtorEmail, amount, @@ -220,8 +202,8 @@ function requestMoney(report, amount, currency, recipientEmail, participant, com chatReportID: chatReport.reportID, transactionID: optimisticReportAction.originalMessage.IOUTransactionID, reportActionID: optimisticReportAction.reportActionID, - createdReportActionID: isNewChat ? optimisticCreatedAction[0].reportActionID : 0, - clientID: optimisticReportAction.sequenceNumber, + createdReportActionID: isNewChat ? optimisticCreatedAction.reportActionID : 0, + shouldKeyReportActionsByID: true, }, {optimisticData, successData, failureData}); Navigation.navigate(ROUTES.getReportRoute(chatReport.reportID)); } @@ -234,8 +216,8 @@ function requestMoney(report, amount, currency, recipientEmail, participant, com * The IOU split has the following shape: * [ * {email: 'currentUser', amount: 100}, - * {email: 'user2', amount: 100, iouReportID: '100', chatReportID: '110', transactionID: '120', reportActionID: '130', clientID: '140'}, - * {email: 'user3', amount: 100, iouReportID: '200', chatReportID: '210', transactionID: '220', reportActionID: '230', clientID: '240'} + * {email: 'user2', amount: 100, iouReportID: '100', chatReportID: '110', transactionID: '120', reportActionID: '130'}, + * {email: 'user3', amount: 100, iouReportID: '200', chatReportID: '210', transactionID: '220', reportActionID: '230'} * ] * @param {Array} participants * @param {String} currentUserLogin @@ -254,10 +236,10 @@ function createSplitsAndOnyxData(participants, currentUserLogin, amount, comment ? chatReports[`${ONYXKEYS.COLLECTION.REPORT}${existingGroupChatReportID}`] : ReportUtils.getChatByParticipants(participantLogins); const groupChatReport = existingGroupChatReport || ReportUtils.buildOptimisticChatReport(participantLogins); - const groupCreatedReportActionData = existingGroupChatReport ? {} : ReportUtils.buildOptimisticCreatedReportAction(currentUserEmail); - const groupChatReportMaxSequenceNumber = lodashGet(groupChatReport, 'maxSequenceNumber', 0); + + // Note: The created action must be optimistically generated before the IOU action so there's no chance that the created action appears after the IOU action in the chat + const groupCreatedReportAction = ReportUtils.buildOptimisticCreatedReportAction(currentUserEmail); const groupIOUReportAction = ReportUtils.buildOptimisticIOUReportAction( - groupChatReportMaxSequenceNumber + 1, CONST.IOU.REPORT_ACTION_TYPE.SPLIT, Math.round(amount * 100), currency, @@ -265,8 +247,6 @@ function createSplitsAndOnyxData(participants, currentUserLogin, amount, comment participants, ); - groupChatReport.maxSequenceNumber = groupChatReportMaxSequenceNumber + 1; - groupChatReport.lastReadSequenceNumber = groupChatReportMaxSequenceNumber + 1; groupChatReport.lastReadTime = DateUtils.getDBTime(); groupChatReport.lastMessageText = groupIOUReportAction.message[0].text; groupChatReport.lastMessageHtml = groupIOUReportAction.message[0].html; @@ -290,8 +270,8 @@ function createSplitsAndOnyxData(participants, currentUserLogin, amount, comment onyxMethod: existingGroupChatReport ? CONST.ONYX.METHOD.MERGE : CONST.ONYX.METHOD.SET, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${groupChatReport.reportID}`, value: { - ...groupCreatedReportActionData, - [groupIOUReportAction.clientID]: groupIOUReportAction, + ...(existingGroupChatReport ? {} : {[groupCreatedReportAction.reportActionID]: groupCreatedReportAction}), + [groupIOUReportAction.reportActionID]: groupIOUReportAction, }, }, ]; @@ -306,8 +286,8 @@ function createSplitsAndOnyxData(participants, currentUserLogin, amount, comment onyxMethod: CONST.ONYX.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${groupChatReport.reportID}`, value: { - 0: {pendingAction: null}, - [groupIOUReportAction.clientID]: {pendingAction: null}, + ...(existingGroupChatReport ? {} : {[groupCreatedReportAction.reportActionID]: {pendingAction: null}}), + [groupIOUReportAction.reportActionID]: {pendingAction: null}, }, }, ]; @@ -324,8 +304,8 @@ function createSplitsAndOnyxData(participants, currentUserLogin, amount, comment onyxMethod: CONST.ONYX.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${groupChatReport.reportID}`, value: { - 0: {pendingAction: null}, - [groupIOUReportAction.clientID]: {pendingAction: null}, + ...(existingGroupChatReport ? {} : {[groupCreatedReportAction.reportActionID]: {pendingAction: null}}), + [groupIOUReportAction.reportActionID]: {pendingAction: null}, }, }, ]; @@ -368,10 +348,9 @@ function createSplitsAndOnyxData(participants, currentUserLogin, amount, comment oneOnOneChatReport.iouReportID = oneOnOneIOUReport.reportID; } - const oneOnOneCreatedReportActionData = existingOneOnOneChatReport ? {} : ReportUtils.buildOptimisticCreatedReportAction(currentUserEmail); - const oneOnOneChatReportMaxSequenceNumber = lodashGet(oneOnOneChatReport, 'maxSequenceNumber', 0); + // Note: The created action must be optimistically generated before the IOU action so there's no chance that the created action appears after the IOU action in the chat + const oneOnOneCreatedReportAction = ReportUtils.buildOptimisticCreatedReportAction(currentUserEmail); const oneOnOneIOUReportAction = ReportUtils.buildOptimisticIOUReportAction( - oneOnOneChatReportMaxSequenceNumber + 1, CONST.IOU.REPORT_ACTION_TYPE.CREATE, splitAmount, currency, @@ -382,10 +361,9 @@ function createSplitsAndOnyxData(participants, currentUserLogin, amount, comment oneOnOneIOUReport.reportID, ); - oneOnOneChatReport.maxSequenceNumber = oneOnOneChatReportMaxSequenceNumber + 1; - oneOnOneChatReport.lastReadSequenceNumber = oneOnOneChatReportMaxSequenceNumber + 1; oneOnOneChatReport.lastMessageText = oneOnOneIOUReportAction.message[0].text; oneOnOneChatReport.lastMessageHtml = oneOnOneIOUReportAction.message[0].html; + if (!existingOneOnOneChatReport) { oneOnOneChatReport.pendingFields = { createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, @@ -402,8 +380,11 @@ function createSplitsAndOnyxData(participants, currentUserLogin, amount, comment onyxMethod: existingOneOnOneChatReport ? CONST.ONYX.METHOD.MERGE : CONST.ONYX.METHOD.SET, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${oneOnOneChatReport.reportID}`, value: { - ...oneOnOneCreatedReportActionData, - [oneOnOneIOUReportAction.clientID]: oneOnOneIOUReportAction, + ...(existingOneOnOneChatReport + ? {} + : {[oneOnOneCreatedReportAction.reportActionID]: oneOnOneCreatedReportAction} + ), + [oneOnOneIOUReportAction.reportActionID]: oneOnOneIOUReportAction, }, }, ); @@ -418,8 +399,11 @@ function createSplitsAndOnyxData(participants, currentUserLogin, amount, comment onyxMethod: CONST.ONYX.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${oneOnOneChatReport.reportID}`, value: { - 0: {pendingAction: null}, - [oneOnOneIOUReportAction.clientID]: {pendingAction: null}, + ...(existingOneOnOneChatReport + ? {} + : {[oneOnOneCreatedReportAction.reportActionID]: {pendingAction: null}} + ), + [oneOnOneIOUReportAction.reportActionID]: {pendingAction: null}, }, }, ); @@ -432,16 +416,17 @@ function createSplitsAndOnyxData(participants, currentUserLogin, amount, comment pendingFields: {createChat: null}, hasOutstandingIOU: existingOneOnOneChatReport ? existingOneOnOneChatReport.hasOutstandingIOU : false, iouReportID: existingOneOnOneChatReport ? existingOneOnOneChatReport.iouReportID : null, - maxSequenceNumber: oneOnOneChatReportMaxSequenceNumber, - lastReadSequenceNumber: oneOnOneChatReportMaxSequenceNumber, }, }, { onyxMethod: CONST.ONYX.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${oneOnOneChatReport.reportID}`, value: { - 0: {pendingAction: null}, - [oneOnOneIOUReportAction.clientID]: {pendingAction: null}, + ...(existingOneOnOneChatReport + ? {} + : {[oneOnOneCreatedReportAction.reportActionID]: {pendingAction: null}} + ), + [oneOnOneIOUReportAction.reportActionID]: {pendingAction: null}, }, }, ); @@ -469,11 +454,10 @@ function createSplitsAndOnyxData(participants, currentUserLogin, amount, comment chatReportID: oneOnOneChatReport.reportID, transactionID: oneOnOneIOUReportAction.originalMessage.IOUTransactionID, reportActionID: oneOnOneIOUReportAction.reportActionID, - clientID: oneOnOneIOUReportAction.clientID.toString(), }; - if (!_.isEmpty(oneOnOneCreatedReportActionData)) { - splitData.createdReportActionID = oneOnOneCreatedReportActionData[0].reportActionID; + if (!_.isEmpty(oneOnOneCreatedReportAction)) { + splitData.createdReportActionID = oneOnOneCreatedReportAction.reportActionID; } splits.push(splitData); @@ -483,11 +467,10 @@ function createSplitsAndOnyxData(participants, currentUserLogin, amount, comment chatReportID: groupChatReport.reportID, transactionID: groupIOUReportAction.originalMessage.IOUTransactionID, reportActionID: groupIOUReportAction.reportActionID, - clientID: groupIOUReportAction.clientID.toString(), }; - if (!_.isEmpty(groupCreatedReportActionData)) { - groupData.createdReportActionID = groupCreatedReportActionData[0].reportActionID; + if (!_.isEmpty(groupCreatedReportAction)) { + groupData.createdReportActionID = groupCreatedReportAction.reportActionID; } return { @@ -518,7 +501,7 @@ function splitBill(participants, currentUserLogin, amount, comment, currency, lo transactionID: groupData.transactionID, reportActionID: groupData.reportActionID, createdReportActionID: groupData.createdReportActionID, - clientID: groupData.clientID, + shouldKeyReportActionsByID: true, }, onyxData); Navigation.dismissModal(); @@ -544,7 +527,7 @@ function splitBillAndOpenReport(participants, currentUserLogin, amount, comment, transactionID: groupData.transactionID, reportActionID: groupData.reportActionID, createdReportActionID: groupData.createdReportActionID, - clientID: groupData.clientID, + shouldKeyReportActionsByID: true, }, onyxData); Navigation.navigate(ROUTES.getReportRoute(groupData.chatReportID)); @@ -566,9 +549,7 @@ function cancelMoneyRequest(chatReportID, iouReportID, type, moneyRequestAction) // Get the amount we are cancelling const amount = moneyRequestAction.originalMessage.amount; - const newSequenceNumber = Report.getMaxSequenceNumber(chatReport.reportID) + 1; const optimisticReportAction = ReportUtils.buildOptimisticIOUReportAction( - newSequenceNumber, type, amount, moneyRequestAction.originalMessage.currency, @@ -582,8 +563,6 @@ function cancelMoneyRequest(chatReportID, iouReportID, type, moneyRequestAction) const currentUserEmail = optimisticReportAction.actorEmail; const updatedIOUReport = IOUUtils.updateIOUOwnerAndTotal(iouReport, currentUserEmail, amount, moneyRequestAction.originalMessage.currency, type); - chatReport.maxSequenceNumber = newSequenceNumber; - chatReport.lastReadSequenceNumber = newSequenceNumber; chatReport.lastMessageText = optimisticReportAction.message[0].text; chatReport.lastMessageHtml = optimisticReportAction.message[0].html; chatReport.hasOutstandingIOU = updatedIOUReport.total !== 0; @@ -593,7 +572,7 @@ function cancelMoneyRequest(chatReportID, iouReportID, type, moneyRequestAction) onyxMethod: CONST.ONYX.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReportID}`, value: { - [optimisticReportAction.sequenceNumber]: { + [optimisticReportAction.reportActionID]: { ...optimisticReportAction, pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, }, @@ -615,7 +594,7 @@ function cancelMoneyRequest(chatReportID, iouReportID, type, moneyRequestAction) onyxMethod: CONST.ONYX.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReportID}`, value: { - [optimisticReportAction.sequenceNumber]: { + [optimisticReportAction.reportActionID]: { pendingAction: null, }, }, @@ -626,7 +605,7 @@ function cancelMoneyRequest(chatReportID, iouReportID, type, moneyRequestAction) onyxMethod: CONST.ONYX.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReportID}`, value: { - [optimisticReportAction.sequenceNumber]: { + [optimisticReportAction.reportActionID]: { pendingAction: null, errors: { [DateUtils.getMicroseconds()]: Localize.translateLocal('iou.error.genericCancelFailureMessage', {type}), @@ -640,7 +619,6 @@ function cancelMoneyRequest(chatReportID, iouReportID, type, moneyRequestAction) transactionID, iouReportID: updatedIOUReport.reportID, comment: '', - clientID: optimisticReportAction.sequenceNumber, cancelMoneyRequestReportActionID: optimisticReportAction.reportActionID, chatReportID, debtorEmail: chatReport.participants[0], @@ -700,11 +678,9 @@ function getSendMoneyParams(report, amount, currency, comment, paymentMethodType } const optimisticIOUReport = ReportUtils.buildOptimisticIOUReport(recipientEmail, managerEmail, amount, chatReport.reportID, currency, preferredLocale, true); - // This will be deprecated soon, in case the migration happens before this PR is merged we'll need to adjust the code here - const newSequenceNumber = Report.getMaxSequenceNumber(chatReport.reportID) + 1; - + // Note: The created action must be optimistically generated before the IOU action so there's no chance that the created action appears after the IOU action in the chat + const optimisticCreatedAction = ReportUtils.buildOptimisticCreatedReportAction(recipientEmail); const optimisticIOUReportAction = ReportUtils.buildOptimisticIOUReportAction( - newSequenceNumber, CONST.IOU.REPORT_ACTION_TYPE.PAY, amount, currency, @@ -716,43 +692,39 @@ function getSendMoneyParams(report, amount, currency, comment, paymentMethodType ); // First, add data that will be used in all cases - const optimisticData = [ - { - onyxMethod: CONST.ONYX.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, - value: { - ...chatReport, - lastVisitedTimestamp: Date.now(), - lastActionCreated: optimisticIOUReportAction.created, - lastReadSequenceNumber: newSequenceNumber, - maxSequenceNumber: newSequenceNumber, - lastMessageText: optimisticIOUReportAction.message[0].text, - lastMessageHtml: optimisticIOUReportAction.message[0].html, - }, + const optimisticChatReportData = { + onyxMethod: CONST.ONYX.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, + value: { + ...chatReport, + lastVisitedTimestamp: Date.now(), + lastActionCreated: optimisticIOUReportAction.created, + lastMessageText: optimisticIOUReportAction.message[0].text, + lastMessageHtml: optimisticIOUReportAction.message[0].html, }, - { - onyxMethod: CONST.ONYX.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.reportID}`, - value: { - [optimisticIOUReportAction.sequenceNumber]: { - ...optimisticIOUReportAction, - pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, - }, + }; + const optimisticIOUReportData = { + onyxMethod: CONST.ONYX.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.REPORT}${optimisticIOUReport.reportID}`, + value: optimisticIOUReport, + }; + const optimisticReportActionsData = { + onyxMethod: CONST.ONYX.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.reportID}`, + value: { + [optimisticIOUReportAction.reportActionID]: { + ...optimisticIOUReportAction, + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, }, }, - { - onyxMethod: CONST.ONYX.METHOD.SET, - key: `${ONYXKEYS.COLLECTION.REPORT}${optimisticIOUReport.reportID}`, - value: optimisticIOUReport, - }, - ]; + }; const successData = [ { onyxMethod: CONST.ONYX.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.reportID}`, value: { - [optimisticIOUReportAction.sequenceNumber]: { + [optimisticIOUReportAction.reportActionID]: { pendingAction: null, }, }, @@ -764,7 +736,7 @@ function getSendMoneyParams(report, amount, currency, comment, paymentMethodType onyxMethod: CONST.ONYX.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.reportID}`, value: { - [optimisticIOUReportAction.sequenceNumber]: { + [optimisticIOUReportAction.reportActionID]: { errors: { [DateUtils.getMicroseconds()]: Localize.translateLocal('iou.error.other'), }, @@ -773,27 +745,32 @@ function getSendMoneyParams(report, amount, currency, comment, paymentMethodType }, ]; - let optimisticCreatedAction; - // Now, let's add the data we need just when we are creating a new chat report if (isNewChat) { - optimisticCreatedAction = ReportUtils.buildOptimisticCreatedReportAction(recipientEmail); - // Change the method to set for new reports because it doesn't exist yet, is faster, // and we need the data to be available when we navigate to the chat page - optimisticData[0].onyxMethod = CONST.ONYX.METHOD.SET; - optimisticData[0].value = { - ...optimisticData[0].value, - pendingFields: {createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD}, - }; - optimisticData[1].onyxMethod = CONST.ONYX.METHOD.SET; - optimisticData[1].value = { - ...optimisticCreatedAction, - ...optimisticData[1].value, - }; - optimisticData[2].onyxMethod = CONST.ONYX.METHOD.SET; + optimisticChatReportData.onyxMethod = CONST.ONYX.METHOD.SET; + optimisticReportActionsData.onyxMethod = CONST.ONYX.METHOD.SET; + optimisticIOUReportData.onyxMethod = CONST.ONYX.METHOD.SET; + + // Set and clear pending fields on the chat report + optimisticChatReportData.value.pendingFields = {createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD}; + successData.push({ + onyxMethod: CONST.ONYX.METHOD.MERGE, + key: optimisticChatReportData.key, + value: {pendingFields: null}, + }); + + // Add an optimistic created action to the optimistic reportActions data + optimisticReportActionsData.value[optimisticCreatedAction.reportActionID] = optimisticCreatedAction; } + const optimisticData = [ + optimisticChatReportData, + optimisticIOUReportData, + optimisticReportActionsData, + ]; + return { params: { iouReportID: optimisticIOUReport.reportID, @@ -801,9 +778,8 @@ function getSendMoneyParams(report, amount, currency, comment, paymentMethodType reportActionID: optimisticIOUReportAction.reportActionID, paymentMethodType, transactionID: optimisticIOUReportAction.originalMessage.IOUTransactionID, - clientID: optimisticIOUReportAction.sequenceNumber, newIOUReportDetails, - createdReportActionID: isNewChat ? optimisticCreatedAction[0].reportActionID : 0, + createdReportActionID: isNewChat ? optimisticCreatedAction.reportActionID : 0, }, optimisticData, successData, @@ -819,10 +795,7 @@ function getSendMoneyParams(report, amount, currency, comment, paymentMethodType * @returns {Object} */ function getPayMoneyRequestParams(chatReport, iouReport, recipient, paymentMethodType) { - const newSequenceNumber = Report.getMaxSequenceNumber(chatReport.reportID) + 1; - const optimisticIOUReportAction = ReportUtils.buildOptimisticIOUReportAction( - newSequenceNumber, CONST.IOU.REPORT_ACTION_TYPE.PAY, iouReport.total, iouReport.currency, @@ -842,8 +815,6 @@ function getPayMoneyRequestParams(chatReport, iouReport, recipient, paymentMetho ...chatReport, lastVisitedTimestamp: Date.now(), lastActionCreated: optimisticIOUReportAction.created, - lastReadSequenceNumber: newSequenceNumber, - maxSequenceNumber: newSequenceNumber, lastMessageText: optimisticIOUReportAction.message[0].text, lastMessageHtml: optimisticIOUReportAction.message[0].html, hasOutstandingIOU: false, @@ -854,7 +825,7 @@ function getPayMoneyRequestParams(chatReport, iouReport, recipient, paymentMetho onyxMethod: CONST.ONYX.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.reportID}`, value: { - [optimisticIOUReportAction.sequenceNumber]: { + [optimisticIOUReportAction.reportActionID]: { ...optimisticIOUReportAction, pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, }, @@ -876,7 +847,7 @@ function getPayMoneyRequestParams(chatReport, iouReport, recipient, paymentMetho onyxMethod: CONST.ONYX.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.reportID}`, value: { - [optimisticIOUReportAction.sequenceNumber]: { + [optimisticIOUReportAction.reportActionID]: { pendingAction: null, }, }, @@ -888,7 +859,7 @@ function getPayMoneyRequestParams(chatReport, iouReport, recipient, paymentMetho onyxMethod: CONST.ONYX.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.reportID}`, value: { - [optimisticIOUReportAction.sequenceNumber]: { + [optimisticIOUReportAction.reportActionID]: { pendingAction: null, errors: { [DateUtils.getMicroseconds()]: Localize.translateLocal('iou.error.other'), @@ -904,7 +875,6 @@ function getPayMoneyRequestParams(chatReport, iouReport, recipient, paymentMetho chatReportID: chatReport.reportID, reportActionID: optimisticIOUReportAction.reportActionID, paymentMethodType, - clientID: optimisticIOUReportAction.sequenceNumber, }, optimisticData, successData, diff --git a/src/libs/actions/Policy.js b/src/libs/actions/Policy.js index 7716c36c7f21..c269accf6ae1 100644 --- a/src/libs/actions/Policy.js +++ b/src/libs/actions/Policy.js @@ -13,7 +13,6 @@ import ROUTES from '../../ROUTES'; import * as OptionsListUtils from '../OptionsListUtils'; import DateUtils from '../DateUtils'; import * as ReportUtils from '../ReportUtils'; -import * as Report from './Report'; import Log from '../Log'; const allPolicies = {}; @@ -97,15 +96,13 @@ function deleteWorkspace(policyID, reports, policyName) { // Add closed actions to all chat reports linked to this policy ..._.map(reports, ({reportID, ownerEmail}) => { - const highestSequenceNumber = Report.getMaxSequenceNumber(reportID); const optimisticClosedReportAction = ReportUtils.buildOptimisticClosedReportAction( - highestSequenceNumber + 1, ownerEmail, policyName, CONST.REPORT.ARCHIVE_REASON.POLICY_DELETED, ); const optimisticReportActions = {}; - optimisticReportActions[optimisticClosedReportAction.clientID] = optimisticClosedReportAction; + optimisticReportActions[optimisticClosedReportAction.reportActionID] = optimisticClosedReportAction; return { onyxMethod: CONST.ONYX.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, @@ -764,171 +761,177 @@ function createWorkspace(ownerEmail = '', makeMeAdmin = false, policyName = '', expenseCreatedReportActionID, }, { - optimisticData: [{ - onyxMethod: CONST.ONYX.METHOD.SET, - key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - value: { - id: policyID, - type: CONST.POLICY.TYPE.FREE, - name: workspaceName, - role: CONST.POLICY.ROLE.ADMIN, - owner: sessionEmail, - outputCurrency: 'USD', - pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, - }, - }, - { - onyxMethod: CONST.ONYX.METHOD.SET, - key: `${ONYXKEYS.COLLECTION.POLICY_MEMBER_LIST}${policyID}`, - value: { - [sessionEmail]: { + optimisticData: [ + { + onyxMethod: CONST.ONYX.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: { + id: policyID, + type: CONST.POLICY.TYPE.FREE, + name: workspaceName, role: CONST.POLICY.ROLE.ADMIN, - errors: {}, + owner: sessionEmail, + outputCurrency: 'USD', + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, }, }, - }, - { - onyxMethod: CONST.ONYX.METHOD.SET, - key: `${ONYXKEYS.COLLECTION.REPORT}${announceChatReportID}`, - value: { - pendingFields: { - addWorkspaceRoom: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + { + onyxMethod: CONST.ONYX.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.POLICY_MEMBER_LIST}${policyID}`, + value: { + [sessionEmail]: { + role: CONST.POLICY.ROLE.ADMIN, + errors: {}, + }, }, - ...announceChatData, }, - }, - { - onyxMethod: CONST.ONYX.METHOD.SET, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${announceChatReportID}`, - value: announceReportActionData, - }, - { - onyxMethod: CONST.ONYX.METHOD.SET, - key: `${ONYXKEYS.COLLECTION.REPORT}${adminsChatReportID}`, - value: { - pendingFields: { - addWorkspaceRoom: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + { + onyxMethod: CONST.ONYX.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.REPORT}${announceChatReportID}`, + value: { + pendingFields: { + addWorkspaceRoom: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + }, + ...announceChatData, }, - ...adminsChatData, }, - }, - { - onyxMethod: CONST.ONYX.METHOD.SET, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${adminsChatReportID}`, - value: adminsReportActionData, - }, - { - onyxMethod: CONST.ONYX.METHOD.SET, - key: `${ONYXKEYS.COLLECTION.REPORT}${expenseChatReportID}`, - value: { - pendingFields: { - addWorkspaceRoom: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + { + onyxMethod: CONST.ONYX.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${announceChatReportID}`, + value: announceReportActionData, + }, + { + onyxMethod: CONST.ONYX.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.REPORT}${adminsChatReportID}`, + value: { + pendingFields: { + addWorkspaceRoom: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + }, + ...adminsChatData, }, - ...expenseChatData, }, - }, - { - onyxMethod: CONST.ONYX.METHOD.SET, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseChatReportID}`, - value: expenseReportActionData, - }], - successData: [{ - onyxMethod: CONST.ONYX.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - value: {pendingAction: null}, - }, - { - onyxMethod: CONST.ONYX.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${announceChatReportID}`, - value: { - pendingFields: { - addWorkspaceRoom: null, + { + onyxMethod: CONST.ONYX.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${adminsChatReportID}`, + value: adminsReportActionData, + }, + { + onyxMethod: CONST.ONYX.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.REPORT}${expenseChatReportID}`, + value: { + pendingFields: { + addWorkspaceRoom: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + }, + ...expenseChatData, }, - pendingAction: null, }, - }, - { - onyxMethod: CONST.ONYX.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${announceChatReportID}`, - value: { - 0: { + { + onyxMethod: CONST.ONYX.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseChatReportID}`, + value: expenseReportActionData, + }, + ], + successData: [ + { + onyxMethod: CONST.ONYX.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, + value: {pendingAction: null}, + }, + { + onyxMethod: CONST.ONYX.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${announceChatReportID}`, + value: { + pendingFields: { + addWorkspaceRoom: null, + }, pendingAction: null, }, }, - }, - { - onyxMethod: CONST.ONYX.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${adminsChatReportID}`, - value: { - pendingFields: { - addWorkspaceRoom: null, + { + onyxMethod: CONST.ONYX.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${announceChatReportID}`, + value: { + [_.keys(announceChatData)[0]]: { + pendingAction: null, + }, }, - pendingAction: null, }, - }, - { - onyxMethod: CONST.ONYX.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${adminsChatReportID}`, - value: { - 0: { + { + onyxMethod: CONST.ONYX.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${adminsChatReportID}`, + value: { + pendingFields: { + addWorkspaceRoom: null, + }, pendingAction: null, }, }, - }, - { - onyxMethod: CONST.ONYX.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${expenseChatReportID}`, - value: { - pendingFields: { - addWorkspaceRoom: null, + { + onyxMethod: CONST.ONYX.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${adminsChatReportID}`, + value: { + [_.keys(adminsChatData)[0]]: { + pendingAction: null, + }, }, - pendingAction: null, }, - }, - { - onyxMethod: CONST.ONYX.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseChatReportID}`, - value: { - 0: { + { + onyxMethod: CONST.ONYX.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${expenseChatReportID}`, + value: { + pendingFields: { + addWorkspaceRoom: null, + }, pendingAction: null, }, }, - }], - failureData: [{ - onyxMethod: CONST.ONYX.METHOD.SET, - key: `${ONYXKEYS.COLLECTION.POLICY_MEMBER_LIST}${policyID}`, - value: null, - }, - { - onyxMethod: CONST.ONYX.METHOD.SET, - key: `${ONYXKEYS.COLLECTION.REPORT}${announceChatReportID}`, - value: null, - }, - { - onyxMethod: CONST.ONYX.METHOD.SET, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${announceChatReportID}`, - value: null, - }, - { - onyxMethod: CONST.ONYX.METHOD.SET, - key: `${ONYXKEYS.COLLECTION.REPORT}${adminsChatReportID}`, - value: null, - }, - { - onyxMethod: CONST.ONYX.METHOD.SET, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${adminsChatReportID}`, - value: null, - }, - { - onyxMethod: CONST.ONYX.METHOD.SET, - key: `${ONYXKEYS.COLLECTION.REPORT}${expenseChatReportID}`, - value: null, - }, - { - onyxMethod: CONST.ONYX.METHOD.SET, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseChatReportID}`, - value: null, - }], + { + onyxMethod: CONST.ONYX.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseChatReportID}`, + value: { + [_.keys(expenseChatData)[0]]: { + pendingAction: null, + }, + }, + }, + ], + failureData: [ + { + onyxMethod: CONST.ONYX.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.POLICY_MEMBER_LIST}${policyID}`, + value: null, + }, + { + onyxMethod: CONST.ONYX.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.REPORT}${announceChatReportID}`, + value: null, + }, + { + onyxMethod: CONST.ONYX.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${announceChatReportID}`, + value: null, + }, + { + onyxMethod: CONST.ONYX.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.REPORT}${adminsChatReportID}`, + value: null, + }, + { + onyxMethod: CONST.ONYX.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${adminsChatReportID}`, + value: null, + }, + { + onyxMethod: CONST.ONYX.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.REPORT}${expenseChatReportID}`, + value: null, + }, + { + onyxMethod: CONST.ONYX.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseChatReportID}`, + value: null, + }, + ], }); Navigation.isNavigationReady() diff --git a/src/libs/actions/Report.js b/src/libs/actions/Report.js index 78faeaa3e3a9..3b4f5468f4e7 100644 --- a/src/libs/actions/Report.js +++ b/src/libs/actions/Report.js @@ -39,14 +39,6 @@ const allReports = {}; let conciergeChatReportID; const typingWatchTimers = {}; -/** - * @param {String} reportID - * @returns {Number} - */ -function getMaxSequenceNumber(reportID) { - return lodashGet(allReports, [reportID, 'maxSequenceNumber'], 0); -} - /** * Get the private pusher channel name for a Report. * @@ -201,51 +193,39 @@ function addActions(reportID, text = '', file) { let attachmentAction; let commandName = 'AddComment'; - const highestSequenceNumber = getMaxSequenceNumber(reportID); - if (text) { - const nextSequenceNumber = highestSequenceNumber + 1; - const reportComment = ReportUtils.buildOptimisticReportAction(nextSequenceNumber, text); + const reportComment = ReportUtils.buildOptimisticAddCommentReportAction(text); reportCommentAction = reportComment.reportAction; reportCommentText = reportComment.commentText; } if (file) { - const nextSequenceNumber = (text && file) ? highestSequenceNumber + 2 : highestSequenceNumber + 1; - // 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 = ReportUtils.buildOptimisticReportAction(nextSequenceNumber, '', file); + const attachment = ReportUtils.buildOptimisticAddCommentReportAction('', file); attachmentAction = attachment.reportAction; } // Always prefer the file as the last action over text const lastAction = attachmentAction || reportCommentAction; - // Our report needs a new maxSequenceNumber that is n larger than the current depending on how many actions we are adding. - const actionCount = text && file ? 2 : 1; - const newSequenceNumber = highestSequenceNumber + actionCount; const currentTime = DateUtils.getDBTime(); - // Update the report in Onyx to have the new sequence number const optimisticReport = { - maxSequenceNumber: newSequenceNumber, lastActionCreated: currentTime, lastMessageText: ReportUtils.formatReportLastMessageText(lastAction.message[0].text), lastActorEmail: currentUserEmail, - lastReadSequenceNumber: newSequenceNumber, lastReadTime: currentTime, }; - // Optimistically add the new actions to the store before waiting to save them to the server. We use the clientID - // so that we can later unset these messages via the server by sending {[clientID]: null} + // Optimistically add the new actions to the store before waiting to save them to the server const optimisticReportActions = {}; if (text) { - optimisticReportActions[reportCommentAction.clientID] = reportCommentAction; + optimisticReportActions[reportCommentAction.reportActionID] = reportCommentAction; } if (file) { - optimisticReportActions[attachmentAction.clientID] = attachmentAction; + optimisticReportActions[attachmentAction.reportActionID] = attachmentAction; } const parameters = { @@ -253,9 +233,8 @@ function addActions(reportID, text = '', file) { reportActionID: file ? attachmentAction.reportActionID : reportCommentAction.reportActionID, commentReportActionID: file && reportCommentAction ? reportCommentAction.reportActionID : null, reportComment: reportCommentText, - clientID: lastAction.clientID, - commentClientID: lodashGet(reportCommentAction, 'clientID', ''), file, + shouldKeyReportActionsByID: true, }; const optimisticData = [ @@ -271,6 +250,22 @@ function addActions(reportID, text = '', file) { }, ]; + const successData = [ + { + onyxMethod: CONST.ONYX.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, + value: _.mapObject(optimisticReportActions, () => ({pendingAction: null})), + }, + ]; + + const failureData = [ + { + onyxMethod: CONST.ONYX.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, + value: _.mapObject(optimisticReportActions, () => null), + }, + ]; + // Update the timezone if it's been 5 minutes from the last time the user added a comment if (DateUtils.canUpdateTimezone()) { const timezone = DateUtils.getCurrentTimezone(); @@ -285,6 +280,8 @@ function addActions(reportID, text = '', file) { API.write(commandName, parameters, { optimisticData, + successData, + failureData, }); // Notify the ReportActionsView that a new comment has arrived @@ -325,54 +322,49 @@ function addComment(reportID, text) { * @param {Object} newReportObject The optimistic report object created when making a new chat, saved as optimistic data */ function openReport(reportID, participantList = [], newReportObject = {}) { - const onyxData = { - optimisticData: [{ - onyxMethod: CONST.ONYX.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, - value: { - isLoadingReportActions: true, - isLoadingMoreReportActions: false, - lastReadTime: DateUtils.getDBTime(), - lastReadSequenceNumber: getMaxSequenceNumber(reportID), - reportName: lodashGet(allReports, [reportID, 'reportName'], CONST.REPORT.DEFAULT_REPORT_NAME), + const optimisticReportData = { + onyxMethod: CONST.ONYX.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, + value: { + isLoadingReportActions: true, + isLoadingMoreReportActions: false, + lastReadTime: DateUtils.getDBTime(), + reportName: lodashGet(allReports, [reportID, 'reportName'], CONST.REPORT.DEFAULT_REPORT_NAME), + }, + }; + const reportSuccessData = { + onyxMethod: CONST.ONYX.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, + value: { + isLoadingReportActions: false, + pendingFields: { + createChat: null, }, - }], - successData: [{ - onyxMethod: CONST.ONYX.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, - value: { - isLoadingReportActions: false, - pendingFields: { - createChat: null, - }, - errorFields: { - createChat: null, - }, - isOptimisticReport: false, + errorFields: { + createChat: null, }, - }], + isOptimisticReport: false, + }, + }; + + const onyxData = { + optimisticData: [optimisticReportData], + successData: [reportSuccessData], }; const params = { reportID, emailList: participantList ? participantList.join(',') : '', + shouldKeyReportActionsByID: true, }; // If we are creating a new report, we need to add the optimistic report data and a report action if (!_.isEmpty(newReportObject)) { // Change the method to set for new reports because it doesn't exist yet, is faster, // and we need the data to be available when we navigate to the chat page - onyxData.optimisticData[0].onyxMethod = CONST.ONYX.METHOD.SET; - - const optimisticCreatedActionData = ReportUtils.buildOptimisticCreatedReportAction(newReportObject.ownerEmail); - onyxData.optimisticData[1] = { - onyxMethod: CONST.ONYX.METHOD.SET, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, - value: optimisticCreatedActionData, - }; - - onyxData.optimisticData[0].value = { - ...onyxData.optimisticData[0].value, + optimisticReportData.onyxMethod = CONST.ONYX.METHOD.SET; + optimisticReportData.value = { + ...optimisticReportData.value, ...newReportObject, pendingFields: { createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, @@ -380,8 +372,20 @@ function openReport(reportID, participantList = [], newReportObject = {}) { isOptimisticReport: true, }; + const optimisticCreatedAction = ReportUtils.buildOptimisticCreatedReportAction(newReportObject.ownerEmail); + onyxData.optimisticData.push({ + onyxMethod: CONST.ONYX.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, + value: {[optimisticCreatedAction.reportActionID]: optimisticCreatedAction}, + }); + onyxData.successData.push({ + onyxMethod: CONST.ONYX.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, + value: {[optimisticCreatedAction.reportActionID]: {pendingAction: null}}, + }); + // Add the createdReportActionID parameter to the API call - params.createdReportActionID = optimisticCreatedActionData[0].reportActionID; + params.createdReportActionID = optimisticCreatedAction.reportActionID; } API.write('OpenReport', params, onyxData); @@ -413,7 +417,10 @@ function navigateToAndOpenReport(userLogins) { */ function reconnect(reportID) { API.write('ReconnectToReport', - {reportID}, + { + reportID, + shouldKeyReportActionsByID: true, + }, { optimisticData: [{ onyxMethod: CONST.ONYX.METHOD.MERGE, @@ -445,13 +452,13 @@ function reconnect(reportID) { * Normally happens when you scroll up on a chat, and the actions have not been read yet. * * @param {String} reportID - * @param {Number} oldestActionSequenceNumber + * @param {String} reportActionID */ -function readOldestAction(reportID, oldestActionSequenceNumber) { +function readOldestAction(reportID, reportActionID) { API.read('ReadOldestAction', { reportID, - reportActionsOffset: oldestActionSequenceNumber, + reportActionID, }, { optimisticData: [{ @@ -488,6 +495,7 @@ function openPaymentDetailsPage(chatReportID, iouReportID) { API.read('OpenPaymentDetailsPage', { reportID: chatReportID, iouReportID, + shouldKeyReportActionsByID: true, }, { optimisticData: [ { @@ -626,7 +634,6 @@ function broadcastUserIsTyping(reportID) { /** * When a report changes in Onyx, 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 */ @@ -662,7 +669,7 @@ Onyx.connect({ * @param {Object} reportAction */ function deleteReportComment(reportID, reportAction) { - const sequenceNumber = reportAction.sequenceNumber; + const reportActionID = reportAction.reportActionID; const deletedMessage = [{ type: 'COMMENT', html: '', @@ -670,7 +677,7 @@ function deleteReportComment(reportID, reportAction) { isEdited: true, }]; const optimisticReportActions = { - [sequenceNumber]: { + [reportActionID]: { pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE, previousMessage: reportAction.message, message: deletedMessage, @@ -693,7 +700,7 @@ function deleteReportComment(reportID, reportAction) { onyxMethod: CONST.ONYX.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, value: { - [sequenceNumber]: { + [reportActionID]: { message: reportAction.message, pendingAction: null, previousMessage: null, @@ -707,7 +714,7 @@ function deleteReportComment(reportID, reportAction) { onyxMethod: CONST.ONYX.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, value: { - [sequenceNumber]: { + [reportActionID]: { pendingAction: null, previousMessage: null, }, @@ -730,8 +737,8 @@ function deleteReportComment(reportID, reportAction) { const parameters = { reportID, - sequenceNumber, reportActionID: reportAction.reportActionID, + shouldKeyReportActionsByID: true, }; API.write('DeleteComment', parameters, {optimisticData, successData, failureData}); } @@ -830,9 +837,9 @@ function editReportComment(reportID, originalReportAction, textForNewComment) { } // Optimistically update the reportAction with the new message - const sequenceNumber = originalReportAction.sequenceNumber; + const reportActionID = originalReportAction.reportActionID; const optimisticReportActions = { - [sequenceNumber]: { + [reportActionID]: { pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, message: [{ isEdited: true, @@ -856,7 +863,7 @@ function editReportComment(reportID, originalReportAction, textForNewComment) { onyxMethod: CONST.ONYX.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, value: { - [sequenceNumber]: { + [reportActionID]: { ...originalReportAction, pendingAction: null, }, @@ -869,7 +876,7 @@ function editReportComment(reportID, originalReportAction, textForNewComment) { onyxMethod: CONST.ONYX.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, value: { - [sequenceNumber]: { + [reportActionID]: { pendingAction: null, }, }, @@ -878,9 +885,9 @@ function editReportComment(reportID, originalReportAction, textForNewComment) { const parameters = { reportID, - sequenceNumber, reportComment: htmlForNewComment, - reportActionID: originalReportAction.reportActionID, + reportActionID, + shouldKeyReportActionsByID: true, }; API.write('UpdateComment', parameters, {optimisticData, successData, failureData}); } @@ -974,7 +981,7 @@ function addPolicyReport(policy, reportName, visibility) { { onyxMethod: CONST.ONYX.METHOD.SET, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${policyReport.reportID}`, - value: createdReportAction, + value: {[createdReportAction.reportActionID]: createdReportAction}, }, ]; const successData = [ @@ -991,7 +998,7 @@ function addPolicyReport(policy, reportName, visibility) { onyxMethod: CONST.ONYX.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${policyReport.reportID}`, value: { - 0: { + [createdReportAction.reportActionID]: { pendingAction: null, }, }, @@ -1191,7 +1198,6 @@ export { updatePolicyRoomName, clearPolicyRoomNameErrors, clearIOUError, - getMaxSequenceNumber, subscribeToNewActionEvent, showReportActionNotification, }; diff --git a/src/libs/actions/ReportActions.js b/src/libs/actions/ReportActions.js index 828cdecdda1d..6205fa847ed2 100644 --- a/src/libs/actions/ReportActions.js +++ b/src/libs/actions/ReportActions.js @@ -3,21 +3,21 @@ import ONYXKEYS from '../../ONYXKEYS'; /** * @param {String} reportID - * @param {String} sequenceNumber + * @param {String} reportActionID */ -function deleteOptimisticReportAction(reportID, sequenceNumber) { +function deleteOptimisticReportAction(reportID, reportActionID) { Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, { - [sequenceNumber]: null, + [reportActionID]: null, }); } /** * @param {String} reportID - * @param {String} sequenceNumber + * @param {String} reportActionID */ -function clearReportActionErrors(reportID, sequenceNumber) { +function clearReportActionErrors(reportID, reportActionID) { Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, { - [sequenceNumber]: { + [reportActionID]: { errors: null, }, }); diff --git a/src/libs/migrateOnyx.js b/src/libs/migrateOnyx.js index e9746a8379f2..6d1a83be5f40 100644 --- a/src/libs/migrateOnyx.js +++ b/src/libs/migrateOnyx.js @@ -5,6 +5,8 @@ import RenameActiveClientsKey from './migrations/RenameActiveClientsKey'; import RenamePriorityModeKey from './migrations/RenamePriorityModeKey'; import MoveToIndexedDB from './migrations/MoveToIndexedDB'; import RenameExpensifyNewsStatus from './migrations/RenameExpensifyNewsStatus'; +import AddLastActionCreated from './migrations/AddLastActionCreated'; +import KeyReportActionsByReportActionID from './migrations/KeyReportActionsByReportActionID'; export default function () { const startTime = Date.now(); @@ -18,6 +20,8 @@ export default function () { RenamePriorityModeKey, AddEncryptedAuthToken, RenameExpensifyNewsStatus, + AddLastActionCreated, + KeyReportActionsByReportActionID, ]; // Reduce all promises down to a single promise. All promises run in a linear fashion, waiting for the diff --git a/src/libs/migrations/AddLastActionCreated.js b/src/libs/migrations/AddLastActionCreated.js index 38c6a715614b..2449efbaf82d 100644 --- a/src/libs/migrations/AddLastActionCreated.js +++ b/src/libs/migrations/AddLastActionCreated.js @@ -12,7 +12,7 @@ export default function () { return new Promise((resolve) => { const connectionID = Onyx.connect({ key: ONYXKEYS.COLLECTION.REPORT, - waitForCollectionCallbacks: true, + waitForCollectionCallback: true, callback: (allReports) => { Onyx.disconnect(connectionID); const reportsToUpdate = {}; @@ -34,13 +34,14 @@ export default function () { if (_.isEmpty(reportsToUpdate)) { Log.info('[Migrate Onyx] Skipped migration AddLastActionCreated'); - } else { - Log.info(`[Migrate Onyx] Adding lastActionCreated field to ${_.keys(reportsToUpdate).length} reports`); - // eslint-disable-next-line rulesdir/prefer-actions-set-data - Onyx.mergeCollection(ONYXKEYS.COLLECTION.REPORT, reportsToUpdate); + return resolve(); } + + Log.info(`[Migrate Onyx] Adding lastActionCreated field to ${_.keys(reportsToUpdate).length} reports`); + // eslint-disable-next-line rulesdir/prefer-actions-set-data + return Onyx.mergeCollection(ONYXKEYS.COLLECTION.REPORT, reportsToUpdate) + .then(resolve); }, }); - return resolve(); }); } diff --git a/src/libs/migrations/KeyReportActionsByReportActionID.js b/src/libs/migrations/KeyReportActionsByReportActionID.js new file mode 100644 index 000000000000..106a3b693af0 --- /dev/null +++ b/src/libs/migrations/KeyReportActionsByReportActionID.js @@ -0,0 +1,59 @@ +import _ from 'underscore'; +import Onyx from 'react-native-onyx'; +import Log from '../Log'; +import ONYXKEYS from '../../ONYXKEYS'; + +/** + * This migration updates reportActions data to be keyed by reportActionID rather than by sequenceNumber. + * + * @returns {Promise} + */ +export default function () { + return new Promise((resolve) => { + const connectionID = Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, + waitForCollectionCallback: true, + callback: (allReportActions) => { + Onyx.disconnect(connectionID); + + if (!allReportActions) { + Log.info('[Migrate Onyx] Skipped migration KeyReportActionsByReportActionID because there were no reportActions'); + return resolve(); + } + + const newReportActions = {}; + const allReportActionsEntires = Object.entries(allReportActions); + for (let i = 0; i < allReportActionsEntires.length; i++) { + const [onyxKey, reportActionsForReport] = allReportActionsEntires[i]; + if (reportActionsForReport) { + const newReportActionsForReport = {}; + const reportActionsForReportEntries = Object.entries(reportActionsForReport); + for (let j = 0; j < reportActionsForReportEntries.length; j++) { + const [reportActionKey, reportAction] = reportActionsForReportEntries[j]; + if (!_.isNaN(Number(reportActionKey)) + && Number(reportActionKey) === Number(reportAction.reportActionID) + && Number(reportActionKey) !== Number(reportAction.sequenceNumber)) { + Log.info('[Migrate Onyx] Skipped migration KeyReportActionsByReportActionID because we already migrated it'); + return resolve(); + } + + // Move it to be keyed by reportActionID instead + newReportActionsForReport[reportAction.reportActionID] = reportAction; + } + newReportActions[onyxKey] = newReportActionsForReport; + } + } + + if (_.isEmpty(newReportActions)) { + Log.info('[Migrate Onyx] Skipped migration KeyReportActionsByReportActionID because there are no actions to migrate'); + return resolve(); + } + + Log.info(`[Migrate Onyx] Re-keying reportActions by reportActionID for ${_.keys(newReportActions).length} reports`); + // eslint-disable-next-line rulesdir/prefer-actions-set-data + return Onyx.multiSet(newReportActions) + .then(resolve); + }, + }); + }); +} diff --git a/src/pages/home/ReportScreen.js b/src/pages/home/ReportScreen.js index 5b665c525b38..d544d78a65e0 100644 --- a/src/pages/home/ReportScreen.js +++ b/src/pages/home/ReportScreen.js @@ -79,7 +79,6 @@ const defaultProps = { isSidebarLoaded: false, reportActions: {}, report: { - maxSequenceNumber: 0, hasOutstandingIOU: false, isLoadingReportActions: false, }, diff --git a/src/pages/home/report/ReportActionItem.js b/src/pages/home/report/ReportActionItem.js index f4a871078ed8..f672b08dce3a 100644 --- a/src/pages/home/report/ReportActionItem.js +++ b/src/pages/home/report/ReportActionItem.js @@ -221,14 +221,9 @@ class ReportActionItem extends Component { { if (this.props.action.pendingAction === CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD) { - const sequenceNumber = this.props.action.actionName - === CONST.REPORT.ACTIONS.TYPE.IOU - ? this.props.action - .sequenceNumber - : this.props.action.clientID; - ReportActions.deleteOptimisticReportAction(this.props.report.reportID, sequenceNumber); + ReportActions.deleteOptimisticReportAction(this.props.report.reportID, this.props.action.reportActionID); } else { - ReportActions.clearReportActionErrors(this.props.report.reportID, this.props.action.sequenceNumber); + ReportActions.clearReportActionErrors(this.props.report.reportID, this.props.action.reportActionID); } }} pendingAction={this.props.draftMessage ? null : this.props.action.pendingAction} diff --git a/src/pages/home/report/ReportActionsList.js b/src/pages/home/report/ReportActionsList.js index 5e61a042193a..38d19864ea5a 100644 --- a/src/pages/home/report/ReportActionsList.js +++ b/src/pages/home/report/ReportActionsList.js @@ -176,7 +176,7 @@ class ReportActionsList extends React.Component { // skeleton view above the created action in a newly generated optimistic chat or one with not // that many comments. const lastReportAction = _.last(this.props.sortedReportActions) || {}; - if (this.props.report.isLoadingReportActions && lastReportAction.sequenceNumber > 0) { + if (this.props.report.isLoadingReportActions && lastReportAction.actionName !== CONST.REPORT.ACTIONS.TYPE.CREATED) { return ( reportActionID + const filteredReportActions = _.filter(reportActions, (reportAction, key) => { + if (!reportAction) { + return false; + } + + if (String(reportAction.sequenceNumber) === key) { + return false; + } + + return true; + }); + + const sortedReportActions = ReportActionsUtils.getSortedReportActions(filteredReportActions, true); return ReportActionsUtils.filterReportActionsForDisplay(sortedReportActions); } @@ -288,19 +300,15 @@ class ReportActionsView extends React.Component { return; } - const minSequenceNumber = _.chain(this.props.reportActions) - .pluck('sequenceNumber') - .min() - .value(); + const oldestReportAction = _.last(this.sortedAndFilteredReportActions); - if (minSequenceNumber === 0) { + // Don't load more chats if we're already at the beginning of the chat history + if (oldestReportAction.actionName === CONST.REPORT.ACTIONS.TYPE.CREATED) { return; } - // Retrieve the next REPORT.ACTIONS.LIMIT sized page of comments, unless we're near the beginning, in which - // case just get everything starting from 0. - const oldestActionSequenceNumber = Math.max(minSequenceNumber - CONST.REPORT.ACTIONS.LIMIT, 0); - Report.readOldestAction(this.props.report.reportID, oldestActionSequenceNumber); + // Retrieve the next REPORT.ACTIONS.LIMIT sized page of comments + Report.readOldestAction(this.props.report.reportID, oldestReportAction.reportActionID); } scrollToBottomAndMarkReportAsRead() { diff --git a/src/pages/home/report/reportActionPropTypes.js b/src/pages/home/report/reportActionPropTypes.js index d3cebe8ba317..f5821dfb9323 100644 --- a/src/pages/home/report/reportActionPropTypes.js +++ b/src/pages/home/report/reportActionPropTypes.js @@ -3,15 +3,15 @@ import PropTypes from 'prop-types'; import reportActionFragmentPropTypes from './reportActionFragmentPropTypes'; export default { + /** The ID of the reportAction. It is the string representation of the a 64-bit integer. */ + reportActionID: PropTypes.string, + /** Name of the action e.g. ADDCOMMENT */ actionName: PropTypes.string, /** Person who created the action */ person: PropTypes.arrayOf(reportActionFragmentPropTypes), - /** ID of the report action */ - sequenceNumber: PropTypes.number, - /** ISO-formatted datetime */ created: PropTypes.string, diff --git a/src/pages/home/sidebar/SidebarLinks.js b/src/pages/home/sidebar/SidebarLinks.js index fe2813db03eb..09a3f9a21d6a 100644 --- a/src/pages/home/sidebar/SidebarLinks.js +++ b/src/pages/home/sidebar/SidebarLinks.js @@ -220,8 +220,6 @@ const chatReportSelector = (report) => { errorFields: { addWorkspaceRoom: report.errorFields && report.errorFields.addWorkspaceRoom, }, - maxSequenceNumber: report.maxSequenceNumber, - lastReadSequenceNumber: report.lastReadSequenceNumber, lastReadTime: report.lastReadTime, lastMessageText: report.lastMessageText, lastActionCreated: report.lastActionCreated, diff --git a/src/pages/iou/IOUModal.js b/src/pages/iou/IOUModal.js index 31c08bb0af3a..e4c3de554774 100755 --- a/src/pages/iou/IOUModal.js +++ b/src/pages/iou/IOUModal.js @@ -358,12 +358,14 @@ class IOUModal extends Component { ); return; } - IOU.requestMoney(this.props.report, + IOU.requestMoney( + this.props.report, Math.round(this.state.amount * 100), this.props.iou.selectedCurrencyCode, this.props.currentUserPersonalDetails.login, selectedParticipants[0], - comment); + comment, + ); } renderHeader() { diff --git a/src/pages/reportPropTypes.js b/src/pages/reportPropTypes.js index 1514d5e3e16c..7251f7a77609 100644 --- a/src/pages/reportPropTypes.js +++ b/src/pages/reportPropTypes.js @@ -34,15 +34,9 @@ export default PropTypes.shape({ /** The time of the last message on the report */ lastActionCreated: PropTypes.string, - /** The sequence number of the last action read by the user */ - lastReadSequenceNumber: PropTypes.number, - /** The last time the report was visited */ lastReadTime: PropTypes.string, - /** The largest sequenceNumber on this report */ - maxSequenceNumber: PropTypes.number, - /** The current user's notification preference for this report */ notificationPreference: PropTypes.oneOfType([ // Some old reports have numbers for the notification preference diff --git a/tests/actions/ReportTest.js b/tests/actions/ReportTest.js index 0918dda3ec40..e0ee0d6be6d6 100644 --- a/tests/actions/ReportTest.js +++ b/tests/actions/ReportTest.js @@ -65,7 +65,7 @@ describe('actions/Report', () => { const TEST_USER_ACCOUNT_ID = 1; const TEST_USER_LOGIN = 'test@test.com'; const REPORT_ID = 1; - const ACTION_ID = 1; + let reportActionID; const REPORT_ACTION = { actionName: CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT, actorAccountID: TEST_USER_ACCOUNT_ID, @@ -74,7 +74,6 @@ describe('actions/Report', () => { avatar: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/avatar_3.png', message: [{type: 'COMMENT', html: 'Testing a comment', text: 'Testing a comment'}], person: [{type: 'TEXT', style: 'strong', text: 'Test User'}], - sequenceNumber: ACTION_ID, shouldShow: true, }; @@ -84,8 +83,6 @@ describe('actions/Report', () => { callback: val => reportActions = val, }); - let clientID; - // Set up Onyx with some test user data return TestHelper.signInWithTestUser(TEST_USER_ACCOUNT_ID, TEST_USER_LOGIN) .then(() => { @@ -101,25 +98,21 @@ describe('actions/Report', () => { }) .then(() => { const resultAction = _.first(_.values(reportActions)); + reportActionID = resultAction.reportActionID; - // Store the generated clientID so that we can send it with our mock Pusher update - clientID = resultAction.clientID; expect(resultAction.message).toEqual(REPORT_ACTION.message); expect(resultAction.person).toEqual(REPORT_ACTION.person); - expect(resultAction.pendingAction).toEqual(CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD); + expect(resultAction.pendingAction).toBeNull(); // We subscribed to the Pusher channel above and now we need to simulate a reportComment action // Pusher event so we can verify that action was handled correctly and merged into the reportActions. const channel = Pusher.getChannel(`${CONST.PUSHER.PRIVATE_USER_CHANNEL_PREFIX}1${CONFIG.PUSHER.SUFFIX}`); - const actionWithoutLoading = {...resultAction}; - delete actionWithoutLoading.pendingAction; channel.emit(Pusher.TYPE.ONYX_API_UPDATE, [ { onyxMethod: CONST.ONYX.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, value: { reportID: REPORT_ID, - maxSequenceNumber: 1, notificationPreference: 'always', lastActionCreated: '2022-11-22 03:48:27.267', lastMessageText: 'Testing a comment', @@ -130,8 +123,7 @@ describe('actions/Report', () => { onyxMethod: CONST.ONYX.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${REPORT_ID}`, value: { - [clientID]: null, - [ACTION_ID]: actionWithoutLoading, + [reportActionID]: {pendingAction: null}, }, }, ]); @@ -145,10 +137,10 @@ describe('actions/Report', () => { // Verify there is only one action and our optimistic comment has been removed expect(_.size(reportActions)).toBe(1); - const resultAction = reportActions[ACTION_ID]; + const resultAction = reportActions[reportActionID]; // Verify that our action is no longer in the loading state - expect(resultAction.pendingAction).not.toBeDefined(); + expect(resultAction.pendingAction).toBeNull(); }); }); @@ -251,11 +243,9 @@ describe('actions/Report', () => { key: `${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, value: { reportID: REPORT_ID, - maxSequenceNumber: 1, notificationPreference: 'always', lastMessageText: 'Comment 1', lastActorEmail: USER_2_LOGIN, - lastReadSequenceNumber: 0, lastActionCreated: reportActionCreatedDate, lastReadTime: DateUtils.subtractMillisecondsFromDateTime(reportActionCreatedDate, 1), }, @@ -272,7 +262,6 @@ describe('actions/Report', () => { avatar: 'https://d2k5nsl2zxldvw.cloudfront.net/images/avatars/avatar_3.png', message: [{type: 'COMMENT', html: 'Comment 1', text: 'Comment 1'}], person: [{type: 'TEXT', style: 'strong', text: 'Test User'}], - sequenceNumber: 1, shouldShow: true, created: reportActionCreatedDate, reportActionID: '1', @@ -347,41 +336,34 @@ describe('actions/Report', () => { person: [{type: 'TEXT', style: 'strong', text: 'Test User'}], shouldShow: true, created: DateUtils.getDBTime(Date.now() - 3), - reportActionID: 'derp1', }; const optimisticReportActions = { onyxMethod: CONST.ONYX.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${REPORT_ID}`, value: { - [_.toArray(reportActions)[1].clientID]: null, - [_.toArray(reportActions)[2].clientID]: null, - [_.toArray(reportActions)[3].clientID]: null, - 2: { + 200: { ...USER_1_BASE_ACTION, message: [{type: 'COMMENT', html: 'Current User Comment 1', text: 'Current User Comment 1'}], created: DateUtils.getDBTime(Date.now() - 2), - sequenceNumber: 2, - reportActionID: 'derp2', + reportActionID: '200', }, - 3: { + 300: { ...USER_1_BASE_ACTION, message: [{type: 'COMMENT', html: 'Current User Comment 2', text: 'Current User Comment 2'}], created: DateUtils.getDBTime(Date.now() - 1), - sequenceNumber: 3, - reportActionID: 'derp3', + reportActionID: '300', }, - 4: { + 400: { ...USER_1_BASE_ACTION, message: [{type: 'COMMENT', html: 'Current User Comment 3', text: 'Current User Comment 3'}], created: DateUtils.getDBTime(), - sequenceNumber: 4, - reportActionID: 'derp4', + reportActionID: '400', }, }, }; reportActionCreatedDate = DateUtils.getDBTime(); - optimisticReportActions.value[4].created = reportActionCreatedDate; + optimisticReportActions.value[400].created = reportActionCreatedDate; // When we emit the events for these pending created actions to update them to not pending channel.emit(Pusher.TYPE.ONYX_API_UPDATE, [ @@ -390,11 +372,9 @@ describe('actions/Report', () => { key: `${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, value: { reportID: REPORT_ID, - maxSequenceNumber: 4, notificationPreference: 'always', lastMessageText: 'Current User Comment 3', lastActorEmail: 'test@test.com', - lastReadSequenceNumber: 4, lastActionCreated: reportActionCreatedDate, lastReadTime: reportActionCreatedDate, }, @@ -406,7 +386,7 @@ describe('actions/Report', () => { }) .then(() => { // If the user deletes a comment that is before the last read - Report.deleteReportComment(REPORT_ID, {...reportActions[2], sequenceNumber: 2, clientID: null}); + Report.deleteReportComment(REPORT_ID, {...reportActions[200]}); return waitForPromisesToResolve(); }) .then(() => { @@ -424,7 +404,7 @@ describe('actions/Report', () => { expect(report.lastReadTime).toBe(DateUtils.subtractMillisecondsFromDateTime(reportActionCreatedDate, 1)); // If the user deletes the last comment after the lastReadTime the lastMessageText will reflect the new last comment - Report.deleteReportComment(REPORT_ID, {...reportActions[4], sequenceNumber: 4, clientID: null}); + Report.deleteReportComment(REPORT_ID, {...reportActions[400]}); return waitForPromisesToResolve(); }) .then(() => { diff --git a/tests/ui/UnreadIndicatorsTest.js b/tests/ui/UnreadIndicatorsTest.js index b2c10c890f75..6ab1d001ca0e 100644 --- a/tests/ui/UnreadIndicatorsTest.js +++ b/tests/ui/UnreadIndicatorsTest.js @@ -152,20 +152,18 @@ function signInAndGetAppWithUnreadChat() { Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, { reportID: REPORT_ID, reportName: CONST.REPORT.DEFAULT_REPORT_NAME, - maxSequenceNumber: 9, - lastReadSequenceNumber: 1, lastReadTime: reportAction3CreatedDate, lastActionCreated: reportAction9CreatedDate, lastMessageText: 'Test', participants: [USER_B_EMAIL], }); + const createdReportActionID = NumberUtils.rand64(); Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${REPORT_ID}`, { - 0: { + [createdReportActionID]: { actionName: CONST.REPORT.ACTIONS.TYPE.CREATED, automatic: false, - sequenceNumber: 0, created: MOMENT_TEN_MINUTES_AGO.clone().format(MOMENT_FORMAT), - reportActionID: NumberUtils.rand64(), + reportActionID: createdReportActionID, message: [ { style: 'strong', @@ -179,15 +177,15 @@ function signInAndGetAppWithUnreadChat() { }, ], }, - 1: TestHelper.buildTestReportComment(USER_B_EMAIL, 1, MOMENT_TEN_MINUTES_AGO.clone().add(10, 'seconds').format(MOMENT_FORMAT), USER_B_ACCOUNT_ID, '1'), - 2: TestHelper.buildTestReportComment(USER_B_EMAIL, 2, MOMENT_TEN_MINUTES_AGO.clone().add(20, 'seconds').format(MOMENT_FORMAT), USER_B_ACCOUNT_ID, '2'), - 3: TestHelper.buildTestReportComment(USER_B_EMAIL, 3, reportAction3CreatedDate, USER_B_ACCOUNT_ID, '3'), - 4: TestHelper.buildTestReportComment(USER_B_EMAIL, 4, MOMENT_TEN_MINUTES_AGO.clone().add(40, 'seconds').format(MOMENT_FORMAT), USER_B_ACCOUNT_ID, '4'), - 5: TestHelper.buildTestReportComment(USER_B_EMAIL, 5, MOMENT_TEN_MINUTES_AGO.clone().add(50, 'seconds').format(MOMENT_FORMAT), USER_B_ACCOUNT_ID, '5'), - 6: TestHelper.buildTestReportComment(USER_B_EMAIL, 6, MOMENT_TEN_MINUTES_AGO.clone().add(60, 'seconds').format(MOMENT_FORMAT), USER_B_ACCOUNT_ID, '6'), - 7: TestHelper.buildTestReportComment(USER_B_EMAIL, 7, MOMENT_TEN_MINUTES_AGO.clone().add(70, 'seconds').format(MOMENT_FORMAT), USER_B_ACCOUNT_ID, '7'), - 8: TestHelper.buildTestReportComment(USER_B_EMAIL, 8, MOMENT_TEN_MINUTES_AGO.clone().add(80, 'seconds').format(MOMENT_FORMAT), USER_B_ACCOUNT_ID, '8'), - 9: TestHelper.buildTestReportComment(USER_B_EMAIL, 9, reportAction9CreatedDate, USER_B_ACCOUNT_ID, '9'), + 1: TestHelper.buildTestReportComment(USER_B_EMAIL, MOMENT_TEN_MINUTES_AGO.clone().add(10, 'seconds').format(MOMENT_FORMAT), USER_B_ACCOUNT_ID, '1'), + 2: TestHelper.buildTestReportComment(USER_B_EMAIL, MOMENT_TEN_MINUTES_AGO.clone().add(20, 'seconds').format(MOMENT_FORMAT), USER_B_ACCOUNT_ID, '2'), + 3: TestHelper.buildTestReportComment(USER_B_EMAIL, reportAction3CreatedDate, USER_B_ACCOUNT_ID, '3'), + 4: TestHelper.buildTestReportComment(USER_B_EMAIL, MOMENT_TEN_MINUTES_AGO.clone().add(40, 'seconds').format(MOMENT_FORMAT), USER_B_ACCOUNT_ID, '4'), + 5: TestHelper.buildTestReportComment(USER_B_EMAIL, MOMENT_TEN_MINUTES_AGO.clone().add(50, 'seconds').format(MOMENT_FORMAT), USER_B_ACCOUNT_ID, '5'), + 6: TestHelper.buildTestReportComment(USER_B_EMAIL, MOMENT_TEN_MINUTES_AGO.clone().add(60, 'seconds').format(MOMENT_FORMAT), USER_B_ACCOUNT_ID, '6'), + 7: TestHelper.buildTestReportComment(USER_B_EMAIL, MOMENT_TEN_MINUTES_AGO.clone().add(70, 'seconds').format(MOMENT_FORMAT), USER_B_ACCOUNT_ID, '7'), + 8: TestHelper.buildTestReportComment(USER_B_EMAIL, MOMENT_TEN_MINUTES_AGO.clone().add(80, 'seconds').format(MOMENT_FORMAT), USER_B_ACCOUNT_ID, '8'), + 9: TestHelper.buildTestReportComment(USER_B_EMAIL, reportAction9CreatedDate, USER_B_ACCOUNT_ID, '9'), }); Onyx.merge(ONYXKEYS.PERSONAL_DETAILS, { [USER_B_EMAIL]: TestHelper.buildPersonalDetails(USER_B_EMAIL, USER_B_ACCOUNT_ID, 'B'), @@ -303,6 +301,8 @@ describe('Unread Indicators', () => { const NEW_REPORT_CREATED_MOMENT = moment().subtract(5, 'seconds'); const NEW_REPORT_FIST_MESSAGE_CREATED_MOMENT = NEW_REPORT_CREATED_MOMENT.add(1, 'seconds'); + const createdReportActionID = NumberUtils.rand64(); + const commentReportActionID = NumberUtils.rand64(); const channel = Pusher.getChannel(`${CONST.PUSHER.PRIVATE_USER_CHANNEL_PREFIX}${USER_A_ACCOUNT_ID}${CONFIG.PUSHER.SUFFIX}`); channel.emit(Pusher.TYPE.ONYX_API_UPDATE, [ { @@ -311,8 +311,6 @@ describe('Unread Indicators', () => { value: { reportID: NEW_REPORT_ID, reportName: CONST.REPORT.DEFAULT_REPORT_NAME, - maxSequenceNumber: 1, - lastReadSequenceNumber: 0, lastReadTime: '', lastActionCreated: DateUtils.getDBTime(NEW_REPORT_FIST_MESSAGE_CREATED_MOMENT.utc().valueOf()), lastMessageText: 'Comment 1', @@ -323,22 +321,20 @@ describe('Unread Indicators', () => { onyxMethod: CONST.ONYX.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${NEW_REPORT_ID}`, value: { - 0: { + [createdReportActionID]: { actionName: CONST.REPORT.ACTIONS.TYPE.CREATED, automatic: false, - sequenceNumber: 0, created: NEW_REPORT_CREATED_MOMENT.format(MOMENT_FORMAT), - reportActionID: NumberUtils.rand64(), + reportActionID: createdReportActionID, }, - 1: { + [commentReportActionID]: { actionName: CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT, actorEmail: USER_C_EMAIL, actorAccountID: USER_C_ACCOUNT_ID, person: [{type: 'TEXT', style: 'strong', text: 'User C'}], - sequenceNumber: 1, created: NEW_REPORT_FIST_MESSAGE_CREATED_MOMENT.format(MOMENT_FORMAT), message: [{type: 'COMMENT', html: 'Comment 1', text: 'Comment 1'}], - reportActionID: NumberUtils.rand64(), + reportActionID: commentReportActionID, }, }, shouldNotify: true, @@ -551,18 +547,12 @@ describe('Unread Indicators', () => { .then(() => { // Simulate the response from the server so that the comment can be deleted in this test lastReportAction = {...CollectionUtils.lastItem(reportActions)}; - delete lastReportAction[lastReportAction.clientID]; Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, { lastMessageText: lastReportAction.message[0].text, lastActionCreated: DateUtils.getDBTime(lastReportAction.timestamp), lastActorEmail: lastReportAction.actorEmail, - maxSequenceNumber: lastReportAction.sequenceNumber, reportID: REPORT_ID, }); - Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${REPORT_ID}`, { - [lastReportAction.clientID]: null, - 10: lastReportAction, - }); return waitForPromisesToResolve(); }) .then(() => { diff --git a/tests/unit/IOUUtilsTest.js b/tests/unit/IOUUtilsTest.js index 94120936631a..97ee16ca038b 100644 --- a/tests/unit/IOUUtilsTest.js +++ b/tests/unit/IOUUtilsTest.js @@ -8,7 +8,6 @@ const managerEmail = 'manager@iou.com'; function createIOUReportAction(type, amount, currency, {IOUTransactionID, isOnline} = {}) { const moneyRequestAction = ReportUtils.buildOptimisticIOUReportAction( - 1, type, amount, currency, diff --git a/tests/unit/MigrationTest.js b/tests/unit/MigrationTest.js index b2d4a81fcd80..00ef88973ce3 100644 --- a/tests/unit/MigrationTest.js +++ b/tests/unit/MigrationTest.js @@ -6,6 +6,7 @@ import Log from '../../src/libs/Log'; import getPlatform from '../../src/libs/getPlatform'; import AddLastActionCreated from '../../src/libs/migrations/AddLastActionCreated'; import MoveToIndexedDB from '../../src/libs/migrations/MoveToIndexedDB'; +import KeyReportActionsByReportActionID from '../../src/libs/migrations/KeyReportActionsByReportActionID'; import ONYXKEYS from '../../src/ONYXKEYS'; jest.mock('../../src/libs/getPlatform'); @@ -20,6 +21,7 @@ describe('Migrations', () => { beforeAll(() => { Onyx.init({keys: ONYXKEYS}); LogSpy = jest.spyOn(Log, 'info'); + Log.serverLoggingCallback = () => {}; return waitForPromisesToResolve(); }); @@ -30,11 +32,18 @@ describe('Migrations', () => { }); describe('MoveToIndexedDb', () => { + let mockMultiSet; beforeEach(() => { getPlatform.mockImplementation(() => CONST.PLATFORM.WEB); - jest.spyOn(Onyx, 'multiSet').mockImplementation(() => Promise.resolve()); + mockMultiSet = jest.spyOn(Onyx, 'multiSet').mockImplementation(() => Promise.resolve()); localStorage.clear(); }); + + afterAll(() => { + mockMultiSet.mockRestore(Onyx, 'multiSet'); + localStorage.clear(); + }); + it('Should do nothing for non web/desktop platforms', () => { // Given the migration is not running on web or desktop getPlatform.mockImplementation(() => CONST.PLATFORM.ANDROID); @@ -92,12 +101,12 @@ describe('Migrations', () => { }); describe('AddLastActionCreated', () => { - it('Should add lastActionCreated wherever lastMessageTimestamp currently is', () => { - Onyx.set(ONYXKEYS.COLLECTION.REPORT, { - report_1: { + it('Should add lastActionCreated wherever lastMessageTimestamp currently is', () => ( + Onyx.multiSet({ + [`${ONYXKEYS.COLLECTION.REPORT}1`]: { lastMessageTimestamp: 1668562273702, }, - report_2: { + [`${ONYXKEYS.COLLECTION.REPORT}2`]: { lastMessageTimestamp: 1668562314821, }, }) @@ -106,7 +115,7 @@ describe('Migrations', () => { expect(LogSpy).toHaveBeenCalledWith('[Migrate Onyx] Adding lastActionCreated field to 2 reports'); const connectionID = Onyx.connect({ key: ONYXKEYS.COLLECTION.REPORT, - waitForCollectionCallbacks: true, + waitForCollectionCallback: true, callback: (allReports) => { Onyx.disconnect(connectionID); expect(_.keys(allReports).length).toBe(2); @@ -117,37 +126,146 @@ describe('Migrations', () => { expect(allReports.report_2.lastActionCreated).toBe('2022-11-16 01:31:54.821'); }, }); - }); - }); + }) + )); - it('Should skip if the report data already has the correct fields', () => { - Onyx.set(ONYXKEYS.COLLECTION.REPORT, { - report_1: { + it('Should skip if the report data already has the correct fields', () => ( + Onyx.multiSet({ + [`${ONYXKEYS.COLLECTION.REPORT}1`]: { lastActionCreated: '2022-11-16 01:31:13.702', }, - report_2: { + [`${ONYXKEYS.COLLECTION.REPORT}2`]: { lastActionCreated: '2022-11-16 01:31:54.821', }, }) .then(AddLastActionCreated) .then(() => { expect(LogSpy).toHaveBeenCalledWith('[Migrate Onyx] Skipped migration AddLastActionCreated'); - }); - }); + }) + )); - it('Should work even if there is no report data', () => { + it('Should work even if there is no report data', () => ( AddLastActionCreated() .then(() => { expect(LogSpy).toHaveBeenCalledWith('[Migrate Onyx] Skipped migration AddLastActionCreated'); const connectionID = Onyx.connect({ key: ONYXKEYS.COLLECTION.REPORT, - waitForCollectionCallbacks: true, + waitForCollectionCallback: true, callback: (allReports) => { Onyx.disconnect(connectionID); expect(allReports).toBeEmpty(); }, }); - }); - }); + }) + )); + }); + + describe('KeyReportActionsByReportActionID', () => { + // Warning: this test has to come before the others in this suite because Onyx.clear leaves traces and keys with null values aren't cleared out between tests + it("Should work even if there's no reportAction data in Onyx", () => ( + KeyReportActionsByReportActionID() + .then(() => expect(LogSpy).toHaveBeenCalledWith('[Migrate Onyx] Skipped migration KeyReportActionsByReportActionID because there were no reportActions')) + )); + + it("Should work even if there's zombie reportAction data in Onyx", () => ( + Onyx.multiSet({ + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]: null, + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}2`]: null, + }) + .then(KeyReportActionsByReportActionID) + .then(() => { + expect(LogSpy).toHaveBeenCalledWith('[Migrate Onyx] Skipped migration KeyReportActionsByReportActionID because there are no actions to migrate'); + const connectionID = Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, + waitForCollectionCallback: true, + callback: (allReportActions) => { + Onyx.disconnect(connectionID); + _.each(allReportActions, reportActionsForReport => expect(reportActionsForReport).toBeNull()); + }, + }); + }) + )); + + it('Should migrate reportActions to be keyed by reportActionID instead of sequenceNumber', () => ( + Onyx.multiSet({ + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]: { + 1: { + reportActionID: '1000', + sequenceNumber: 1, + }, + 2: { + reportActionID: '2000', + sequenceNumber: 2, + }, + }, + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}2`]: { + 1: { + reportActionID: '3000', + sequenceNumber: 1, + }, + 2: { + reportActionID: '4000', + sequenceNumber: 2, + }, + }, + }) + .then(KeyReportActionsByReportActionID) + .then(() => { + expect(LogSpy).toHaveBeenCalledWith('[Migrate Onyx] Re-keying reportActions by reportActionID for 2 reports'); + const connectionID = Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, + waitForCollectionCallback: true, + callback: (allReportActions) => { + Onyx.disconnect(connectionID); + expect(_.keys(allReportActions).length).toBe(2); + _.each(allReportActions, (reportActionsForReport) => { + _.each(reportActionsForReport, (reportAction, key) => { + expect(key).toBe(reportAction.reportActionID); + }); + }); + }, + }); + }) + )); + + it('Should return early if the migration has already happened', () => ( + Onyx.multiSet({ + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}1`]: { + 1000: { + reportActionID: '1000', + sequenceNumber: 1, + }, + 2000: { + reportActionID: '2000', + sequenceNumber: 2, + }, + }, + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}2`]: { + 3000: { + reportActionID: '3000', + }, + 4000: { + reportActionID: '4000', + }, + }, + }) + .then(KeyReportActionsByReportActionID) + .then(() => { + expect(LogSpy).toHaveBeenCalledWith('[Migrate Onyx] Skipped migration KeyReportActionsByReportActionID because we already migrated it'); + const connectionID = Onyx.connect({ + key: ONYXKEYS.COLLECTION.REPORT_ACTIONS, + waitForCollectionCallback: true, + callback: (allReportActions) => { + Onyx.disconnect(connectionID); + expect(_.keys(allReportActions).length).toBe(2); + _.each(allReportActions, (reportActionsForReport) => { + _.each(reportActionsForReport, (reportAction, key) => { + expect(key).toBe(reportAction.reportActionID); + }); + }); + }, + }); + }) + )); }); }); diff --git a/tests/unit/OptionsListUtilsTest.js b/tests/unit/OptionsListUtilsTest.js index 01b4e53338b9..a55b1389cf76 100644 --- a/tests/unit/OptionsListUtilsTest.js +++ b/tests/unit/OptionsListUtilsTest.js @@ -5,8 +5,6 @@ import ONYXKEYS from '../../src/ONYXKEYS'; import waitForPromisesToResolve from '../utils/waitForPromisesToResolve'; import CONST from '../../src/CONST'; -const TEST_MAX_SEQUENCE_NUMBER = 10; - describe('OptionsListUtils', () => { // Given a set of reports with both single participants and multiple participants some pinned and some not const REPORTS = { @@ -17,8 +15,6 @@ describe('OptionsListUtils', () => { reportID: 1, participants: ['tonystark@expensify.com', 'reedrichards@expensify.com'], reportName: 'Iron Man, Mister Fantastic', - lastReadSequenceNumber: TEST_MAX_SEQUENCE_NUMBER - 1, - maxSequenceNumber: TEST_MAX_SEQUENCE_NUMBER, hasDraft: true, }, 2: { @@ -28,8 +24,6 @@ describe('OptionsListUtils', () => { reportID: 2, participants: ['peterparker@expensify.com'], reportName: 'Spider-Man', - lastReadSequenceNumber: TEST_MAX_SEQUENCE_NUMBER - 1, - maxSequenceNumber: TEST_MAX_SEQUENCE_NUMBER, }, // This is the only report we are pinning in this test @@ -40,8 +34,6 @@ describe('OptionsListUtils', () => { reportID: 3, participants: ['reedrichards@expensify.com'], reportName: 'Mister Fantastic', - lastReadSequenceNumber: TEST_MAX_SEQUENCE_NUMBER, - maxSequenceNumber: TEST_MAX_SEQUENCE_NUMBER, }, 4: { lastReadTime: '2021-01-14 11:25:39.298', @@ -50,8 +42,6 @@ describe('OptionsListUtils', () => { reportID: 4, participants: ['tchalla@expensify.com'], reportName: 'Black Panther', - lastReadSequenceNumber: TEST_MAX_SEQUENCE_NUMBER - 1, - maxSequenceNumber: TEST_MAX_SEQUENCE_NUMBER, }, 5: { lastReadTime: '2021-01-14 11:25:39.299', @@ -60,8 +50,6 @@ describe('OptionsListUtils', () => { reportID: 5, participants: ['suestorm@expensify.com'], reportName: 'Invisible Woman', - lastReadSequenceNumber: TEST_MAX_SEQUENCE_NUMBER - 1, - maxSequenceNumber: TEST_MAX_SEQUENCE_NUMBER, }, 6: { lastReadTime: '2021-01-14 11:25:39.300', @@ -70,8 +58,6 @@ describe('OptionsListUtils', () => { reportID: 6, participants: ['thor@expensify.com'], reportName: 'Thor', - lastReadSequenceNumber: TEST_MAX_SEQUENCE_NUMBER, - maxSequenceNumber: TEST_MAX_SEQUENCE_NUMBER, }, // Note: This report has the largest lastActionCreated @@ -82,8 +68,6 @@ describe('OptionsListUtils', () => { reportID: 7, participants: ['steverogers@expensify.com'], reportName: 'Captain America', - lastReadSequenceNumber: TEST_MAX_SEQUENCE_NUMBER - 1, - maxSequenceNumber: TEST_MAX_SEQUENCE_NUMBER, }, // Note: This report has no lastActionCreated @@ -94,8 +78,6 @@ describe('OptionsListUtils', () => { reportID: 8, participants: ['galactus_herald@expensify.com'], reportName: 'Silver Surfer', - lastReadSequenceNumber: TEST_MAX_SEQUENCE_NUMBER, - maxSequenceNumber: TEST_MAX_SEQUENCE_NUMBER, }, // Note: This report has an IOU @@ -106,8 +88,6 @@ describe('OptionsListUtils', () => { reportID: 9, participants: ['mistersinister@marauders.com'], reportName: 'Mister Sinister', - lastReadSequenceNumber: TEST_MAX_SEQUENCE_NUMBER, - maxSequenceNumber: TEST_MAX_SEQUENCE_NUMBER, iouReportID: 100, hasOutstandingIOU: true, }, @@ -123,8 +103,6 @@ describe('OptionsListUtils', () => { oldPolicyName: "SHIELD's workspace", chatType: CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT, isOwnPolicyExpenseChat: true, - lastReadSequenceNumber: TEST_MAX_SEQUENCE_NUMBER - 1, - maxSequenceNumber: TEST_MAX_SEQUENCE_NUMBER, }, }; @@ -185,8 +163,6 @@ describe('OptionsListUtils', () => { reportID: 11, participants: ['concierge@expensify.com'], reportName: 'Concierge', - lastReadSequenceNumber: TEST_MAX_SEQUENCE_NUMBER - 1, - maxSequenceNumber: TEST_MAX_SEQUENCE_NUMBER, }, }; @@ -199,8 +175,6 @@ describe('OptionsListUtils', () => { reportID: 12, participants: ['chronos@expensify.com'], reportName: 'Chronos', - lastReadSequenceNumber: TEST_MAX_SEQUENCE_NUMBER - 1, - maxSequenceNumber: TEST_MAX_SEQUENCE_NUMBER, }, }; @@ -213,8 +187,6 @@ describe('OptionsListUtils', () => { reportID: 13, participants: ['receipts@expensify.com'], reportName: 'Receipts', - lastReadSequenceNumber: TEST_MAX_SEQUENCE_NUMBER - 1, - maxSequenceNumber: TEST_MAX_SEQUENCE_NUMBER, }, }; diff --git a/tests/unit/SidebarOrderTest.js b/tests/unit/SidebarOrderTest.js index b7d7ff873662..2b44f2f06243 100644 --- a/tests/unit/SidebarOrderTest.js +++ b/tests/unit/SidebarOrderTest.js @@ -92,15 +92,12 @@ describe('Sidebar', () => { // Given three unread reports in the recently updated order of 3, 2, 1 const report1 = { ...LHNTestUtils.getFakeReport(['email1@test.com', 'email2@test.com'], 3), - lastReadSequenceNumber: LHNTestUtils.TEST_MAX_SEQUENCE_NUMBER - 1, }; const report2 = { ...LHNTestUtils.getFakeReport(['email3@test.com', 'email4@test.com'], 2), - lastReadSequenceNumber: LHNTestUtils.TEST_MAX_SEQUENCE_NUMBER - 1, }; const report3 = { ...LHNTestUtils.getFakeReport(['email5@test.com', 'email6@test.com'], 1), - lastReadSequenceNumber: LHNTestUtils.TEST_MAX_SEQUENCE_NUMBER - 1, }; return waitForPromisesToResolve() diff --git a/tests/utils/LHNTestUtils.js b/tests/utils/LHNTestUtils.js index 8aee1b7502e1..0079313b9cc0 100644 --- a/tests/utils/LHNTestUtils.js +++ b/tests/utils/LHNTestUtils.js @@ -7,8 +7,6 @@ import SidebarLinks from '../../src/pages/home/sidebar/SidebarLinks'; import CONST from '../../src/CONST'; import DateUtils from '../../src/libs/DateUtils'; -const TEST_MAX_SEQUENCE_NUMBER = 10; - const fakePersonalDetails = { 'email1@test.com': { login: 'email1@test.com', @@ -79,8 +77,6 @@ function getFakeReport(participants = ['email1@test.com', 'email2@test.com'], mi return { reportID: `${++lastFakeReportID}`, reportName: 'Report', - maxSequenceNumber: TEST_MAX_SEQUENCE_NUMBER, - lastReadSequenceNumber: TEST_MAX_SEQUENCE_NUMBER, lastActionCreated, lastReadTime: isUnread ? DateUtils.subtractMillisecondsFromDateTime(lastActionCreated, 1) : lastActionCreated, participants, @@ -106,7 +102,6 @@ function getAdvancedFakeReport(isArchived, isUserCreatedPolicyRoom, hasAddWorksp statusNum: isArchived ? CONST.REPORT.STATUS.CLOSED : 0, stateNum: isArchived ? CONST.REPORT.STATE_NUM.SUBMITTED : 0, errorFields: hasAddWorkspaceError ? {addWorkspaceRoom: 'blah'} : null, - lastReadSequenceNumber: isUnread ? TEST_MAX_SEQUENCE_NUMBER - 1 : TEST_MAX_SEQUENCE_NUMBER, isPinned, hasDraft, }; @@ -172,5 +167,4 @@ export { getDefaultRenderedSidebarLinks, getAdvancedFakeReport, getFakeReport, - TEST_MAX_SEQUENCE_NUMBER, }; diff --git a/tests/utils/TestHelper.js b/tests/utils/TestHelper.js index 84fdea3f1587..8968ab28e8a1 100644 --- a/tests/utils/TestHelper.js +++ b/tests/utils/TestHelper.js @@ -156,21 +156,19 @@ function setPersonalDetails(login, accountID) { /** * @param {String} actorEmail - * @param {Number} sequenceNumber * @param {String} created * @param {Number} actorAccountID * @param {String} actionID * @returns {Object} */ -function buildTestReportComment(actorEmail, sequenceNumber, created, actorAccountID, actionID = null) { +function buildTestReportComment(actorEmail, created, actorAccountID, actionID = null) { const reportActionID = actionID || NumberUtils.rand64(); return { actionName: CONST.REPORT.ACTIONS.TYPE.ADDCOMMENT, actorEmail, person: [{type: 'TEXT', style: 'strong', text: 'User B'}], - sequenceNumber, created, - message: [{type: 'COMMENT', html: `Comment ${sequenceNumber}`, text: `Comment ${sequenceNumber}`}], + message: [{type: 'COMMENT', html: `Comment ${actionID}`, text: `Comment ${actionID}`}], reportActionID, actorAccountID, };