Expensify / App

Welcome to New Expensify: a complete re-imagination of financial collaboration, centered around chat. Help us build the next generation of Expensify by sharing feedback and contributing to the code.
https://new.expensify.com
MIT License
3.56k stars 2.9k forks source link

[HOLD for payment 2023-08-28] [$1000] Offline mode: Crash after deleting Split Bill request #25481

Closed izarutskaya closed 1 year ago

izarutskaya commented 1 year ago

If you haven’t already, check out our contributing guidelines for onboarding and email contributors@expensify.com to request to join our Slack channel!


Action Performed:

  1. Log in to any account.
  2. Turn OFF the network connection to switch to OFFLINE mode.
  3. Create a Split Bill request with two other users.
  4. This will generate two sub-reports: report A and report B >> Click on report A to view its details.
  5. Hover over the report and select the Delete option >> report A will be deleted.
  6. Click on report B >> Observation: The app CRASHES.

Expected Result:

App should not crash

Actual Result:

App Crashes

Workaround:

Unknown

Platforms:

Which of our officially supported platforms is this issue occurring on?

Version Number: v1.3.55-1

Reproducible in staging?: Y

Reproducible in production?: N

If this was caught during regression testing, add the test name, ID and link from TestRail:

Email or phone of affected tester (no customers):

Logs: https://stackoverflow.com/c/expensify/questions/4856

Notes/Photos/Videos: Any additional supporting documentation

https://github.com/Expensify/App/assets/115492554/468c2e32-5ef0-499c-a4bb-652085a118e5

https://github.com/Expensify/App/assets/115492554/677b458c-c71e-47b5-9139-77b5d738a1e2

Expensify/Expensify Issue URL:

Issue reported by: @tranvantoan-qn

Slack conversation: https://expensify.slack.com/archives/C049HHMV9SM/p1692279822439699

View all open jobs on GitHub

Upwork Automation - Do Not Edit
  • Upwork Job URL: https://www.upwork.com/jobs/~01e588feb8af6feae9
  • Upwork Job ID: 1692566105949474816
  • Last Price Increase: 2023-08-18
  • Automatic offers:
    • pradeepmdk | Contributor | 26243141
    • tranvantoan-qn | Reporter | 26243145
melvin-bot[bot] commented 1 year ago

Triggered auto assignment to @MitchExpensify (Bug), see https://stackoverflow.com/c/expensify/questions/14418 for more details.

melvin-bot[bot] commented 1 year ago

Bug0 Triage Checklist (Main S/O)

OSBotify commented 1 year ago

:wave: Friendly reminder that deploy blockers are time-sensitive ⏱ issues! Check out the open StagingDeployCash deploy checklist to see the list of PRs included in this release, then work quickly to do one of the following:

  1. Identify the pull request that introduced this issue and revert it.
  2. Find someone who can quickly fix the issue.
  3. Fix the issue yourself.
melvin-bot[bot] commented 1 year ago

Triggered auto assignment to @johnmlee101 (Engineering), see https://stackoverflow.com/c/expensify/questions/4319 for more details.

tranvantoan-qn commented 1 year ago

FYI: This crash happens on ALL platforms, not only Android as specified in the issue Title

pradeepmdk commented 1 year ago

Proposal

Please re-state the problem that we are trying to solve in this issue.

Offline mode: Crash after deleting Split Bill request

What is the root cause of that problem?

Screenshot 2023-08-18 at 9 28 05 PM

in offline after deleting transaction its updated to null because its completed removed from onyx Screenshot 2023-08-18 at 8 13 52 PM Screenshot 2023-08-18 at 8 18 19 PM

https://github.com/Expensify/App/blob/a068ca5366c0026b35d9cab7949ec17965f0b5d8/src/libs/TransactionUtils.js?#L196

https://github.com/Expensify/App/blob/a068ca5366c0026b35d9cab7949ec17965f0b5d8/src/libs/actions/IOU.js?#L1109-L1113 here when offline mode optimisticData will execute so it's set as a null. when onyx null it will remove from local database

What changes do you think we should make in order to solve the problem?

1) we need to check its null or not

function getAllReportTransactions(reportID) {
    return _.filter(allTransactions, (transaction) => transaction && transaction.reportID === reportID);
}

And here we need update https://github.com/Expensify/App/blob/a068ca5366c0026b35d9cab7949ec17965f0b5d8/src/components/ReportActionItem/MoneyRequestAction.js?#L133-L136 to

IOUUtils.isIOUReportPendingCurrencyConversion(props.iouReport);

because this isIOUReportPendingCurrencyConversion is expecting the iouReport param only https://github.com/Expensify/App/blob/a068ca5366c0026b35d9cab7949ec17965f0b5d8/src/libs/IOUUtils.js?#L77-L79

This Pr is modified the function https://github.com/Expensify/App/pull/24345/files

result

https://github.com/Expensify/App/assets/16595846/f4a2b2c5-34a3-4d67-b8c4-28cc69e254e8

MitchExpensify commented 1 year ago

Having trouble reproducing this on a virtual device via Browserstack

image
pradeepmdk commented 1 year ago

@MitchExpensify you can verify on the web also. because its not a platform specfic

MitchExpensify commented 1 year ago

Ok awesome, trying that now @pradeepmdk !

tranvantoan-qn commented 1 year ago

On mobile, for the crash to occur, we will need to return to the LHN and then navigate to other reports, as demonstrated here:

https://github.com/Expensify/App/assets/10254571/3a1b0bfc-54bc-4789-a55a-e3fe4776a3b2

MitchExpensify commented 1 year ago

Reproduced!

melvin-bot[bot] commented 1 year ago

Job added to Upwork: https://www.upwork.com/jobs/~01e588feb8af6feae9

melvin-bot[bot] commented 1 year ago

Current assignee @MitchExpensify is eligible for the External assigner, not assigning anyone new.

melvin-bot[bot] commented 1 year ago

Triggered auto assignment to Contributor-plus team member for initial proposal review - @ArekChr (External)

Pujan92 commented 1 year ago

Proposal

Please re-state the problem that we are trying to solve in this issue.

Crash after deleting split bill in offline mode

What is the root cause of that problem?

For the deletion of money request we set the value null within the optimistic data for that specific transaction. After that whenever we try to fetch all transactions for any report below condition(accessing reportID of null) get fails to execute and crashes the app. https://github.com/Expensify/App/blob/dd2c98b839b0aa85f4ffa3bc6f230cf1d499183d/src/libs/actions/IOU.js#L1113

What changes do you think we should make in order to solve the problem?

Instead of setting null, we can set an empty object {} for the optimistic data and null has been received in the api success response onyx data. With that accessing reportID on null won't occur and the app won't get crashed. value: {}

johnmlee101 commented 1 year ago

I like the simplicity of your proposal @Pujan92 . I see that @pradeepmdk you edited your post with the same solution after the proposal by @Pujan92 so I'm going to go with this solution!

johnmlee101 commented 1 year ago

I'll confirm one last time to see

pradeepmdk commented 1 year ago

@johnmlee101 I added this initially

 const optimistic data = [ 
     { 
         onyxMethod: Onyx.METHOD.SET, 
         key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, 
         value: null, 

forgot to save this on the solution you can check my edit history.

johnmlee101 commented 1 year ago

I see your edit at 1:57pm ET on your post and @Pujan92 's post was 1:18pm ET. So he had the solution out initially.

pradeepmdk commented 1 year ago

@johnmlee101 sorry, that is my mistake I keep it open in the preview mode and forgot to commit. in the emergency went out. once back I saw it in preview mode. so at the time, I did not see him post.

pradeepmdk commented 1 year ago

@johnmlee101 @marcaaron can you check my first solution

Pujan92 commented 1 year ago

As @marcaaron reacted with 👎 for my proposal, I agree that it isn't the proper RCA and solution.

What is the root cause of that problem?

1) When there is an add money request pending and if we delete the other money request it will crash the app due to trying to access reportID on null. Offline mode is needed to make the requests pending. https://github.com/Expensify/App/blob/03cf0b14f82a41db004dfee27799e3cea1608ee5/src/components/ReportActionItem/MoneyRequestAction.js#L132-L135 https://github.com/Expensify/App/blob/03cf0b14f82a41db004dfee27799e3cea1608ee5/src/libs/IOUUtils.js#L77-L78 https://github.com/Expensify/App/blob/03cf0b14f82a41db004dfee27799e3cea1608ee5/src/libs/TransactionUtils.js#L195-L196

2) When we set value null for the onyx key it should remove the key from the storage. https://github.com/Expensify/react-native-onyx/blob/79bfb5ef713e288fea8827cb4efe0d677177a525/lib/Onyx.js#L937-L939

But inside the remove function of onyx we notify the subscribers with the changed value if we have set the callback and waitForCollectionCallback. https://github.com/Expensify/react-native-onyx/blob/79bfb5ef713e288fea8827cb4efe0d677177a525/lib/Onyx.js#L508-L515 https://github.com/Expensify/App/blob/03cf0b14f82a41db004dfee27799e3cea1608ee5/src/libs/TransactionUtils.js#L11-L20

3) With this it will first update the subscriber by passing that null value entry with the callback execution and then it will remove the key from the storage. Meanwhile, we are calling the getAllReportTransactions and it tries to operate null transaction before it gets removed from storage.

What changes do you think we should make in order to solve the problem?

We can filter out the null values for the transactions in the Onyx callback we set in the TransactionUtils.js file. https://github.com/Expensify/App/blob/03cf0b14f82a41db004dfee27799e3cea1608ee5/src/libs/TransactionUtils.js#L11-L20

allTransactions = _.filter(val, (transaction => transaction));

Above are my observations. Kindly review it @johnmlee101 @marcaaron

pradeepmdk commented 1 year ago

@johnmlee101 @marcaaron proposal updated https://github.com/Expensify/App/issues/25481#issuecomment-1684031967

mountiny commented 1 year ago

We should use null values when deleting, thats probably why there were 👎

Pujan92 commented 1 year ago

Yes @mountiny, I agree and that's why I have made a new proposal by digging more into the issue. Even I also reacted with :-1: considering the same reason later wards 😅

pradeepmdk commented 1 year ago

@mountiny @johnmlee101 can check my proposal I have PR for this issue https://github.com/Expensify/App/issues/25481#issuecomment-1684031967

mountiny commented 1 year ago

I think @Pujan92 Solution seems simpler if its all that needs to be done.

pradeepmdk commented 1 year ago

@mountiny we need to update this as well

IOUUtils.isIOUReportPendingCurrencyConversion(props.iouReport);

and I also have the filter check this is was I initially proposed

function getAllReportTransactions(reportID) {
    return _.filter(allTransactions, (transaction) => transaction && transaction.reportID === reportID);
}

but I agree with the @Pujan92 solution as well.

luacmartins commented 1 year ago

I agree that we should use null to remove the transactions. It seems that @pradeepmdk's solution captures both changes needed, i.e. filter out the null transaction and update getAllReportTransactions params. I think we should go with their solution here (although we could incorporate part of @Pujan92's solution and filter at the Onyx connect level). @johnmlee101 is the CME here, so I'll let him make the final call.

melvin-bot[bot] commented 1 year ago

📣 @pradeepmdk 🎉 An offer has been automatically sent to your Upwork account for the Contributor role 🎉 Thanks for contributing to the Expensify app!

Offer link Upwork job Please accept the offer and leave a comment on the Github issue letting us know when we can expect a PR to be ready for review 🧑‍💻 Keep in mind: Code of Conduct | Contributing 📖

melvin-bot[bot] commented 1 year ago

📣 @tranvantoan-qn 🎉 An offer has been automatically sent to your Upwork account for the Reporter role 🎉 Thanks for contributing to the Expensify app!

Offer link Upwork job

Pujan92 commented 1 year ago

IOUUtils.isIOUReportPendingCurrencyConversion(props.iouReport);

Yes, this change also needed to correct params. Thanks :) Just to add above point is not causing the actual crash as it is coming due to null transaction only.

roryabraham commented 1 year ago

I'm a bit confused why doing this:

{
    onyxMethod: Onyx.METHOD.SET,
    key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`,
    value: null,
},

is resulting in null being included in allTransactions here. I'd say that's the real root cause for this issue. However, I just adding a null-safe accessor is fine for now because this is a deploy blocker.

Pujan92 commented 1 year ago

@roryabraham As per my observation it is due to notify the subscribers with the changed values before updating the storage. https://github.com/Expensify/react-native-onyx/blob/79bfb5ef713e288fea8827cb4efe0d677177a525/lib/Onyx.js#L508-L515

roryabraham commented 1 year ago

Created PR for this: https://github.com/Expensify/App/pull/25610

we can discuss payments for this after, but because this is a deploy blocker it should never have been marked as External

pradeepmdk commented 1 year ago

@roryabraham PR is ready to review

roryabraham commented 1 year ago

@pradeepmdk PR is missing screenshots / screen recordings on all platforms. Please complete those ASAP so we can resolve this urgent issue

pradeepmdk commented 1 year ago

working on it 15 minutes i will complete

pradeepmdk commented 1 year ago

@roryabraham need some more time pod is falling checking on that

pradeepmdk commented 1 year ago

@roryabraham can we try adhoc build

melvin-bot[bot] commented 1 year ago

🎯 ⚡️ Woah @ArekChr / @pradeepmdk, great job pushing this forwards! ⚡️

The pull request got merged within 3 working days of assignment, so this job is eligible for a 50% #urgency bonus 🎉

On to the next one 🚀

roryabraham commented 1 year ago

@MitchExpensify removing @ArekChr since they have not commented on this issue and did not review the PR before we merged it. So no C+ payment due here

MitchExpensify commented 1 year ago

Payment summary:

Reporting - $250 via Upwork to @tranvantoan-qn Contributor - $1500 via Upwork to @pradeepmdk

melvin-bot[bot] commented 1 year ago

Reviewing label has been removed, please complete the "BugZero Checklist".

melvin-bot[bot] commented 1 year ago

The solution for this issue has been :rocket: deployed to production :rocket: in version 1.3.55-8 and is now subject to a 7-day regression period :calendar:. Here is the list of pull requests that resolve this issue:

If no regressions arise, payment will be issued on 2023-08-28. :confetti_ball:

After the hold period is over and BZ checklist items are completed, please complete any of the applicable payments for this issue, and check them off once done.

For reference, here are some details about the assignees on this issue:

As a reminder, here are the bonuses/penalties that should be applied for any External issue:

melvin-bot[bot] commented 1 year ago

BugZero Checklist: The PR fixing this issue has been merged! The following checklist (instructions) will need to be completed before the issue can be closed:

marcaaron commented 1 year ago

is resulting in null being included in allTransactions here. I'd say that's the real root cause for this issue

Hmm actually - I think @roryabraham you are suggesting something like:

Given this setup:

When this happens:

We would expect this:

If so, that makes sense to me and does sound like the real root cause. We should fix it in Onyx?

pradeepmdk commented 1 year ago

@marcaaron Yes, if we fixed it in Onyx that is the best solution here

roryabraham commented 1 year ago

Agree @marcaaron that's the real root cause and we should fix it in Onyx. Didn't do that today because I wanted to get the deploy out as quickly as possible, but I made a follow-up issue here: https://github.com/Expensify/react-native-onyx/issues/310

Pujan92 commented 1 year ago
  • The subscriber to get the new complete collection with any null values removed
        if (_.isFunction(subscriber.callback)) {
            if (isCollectionKey(subscriber.key) && subscriber.waitForCollectionCallback) {
                const cachedCollection = getCachedCollection(subscriber.key);
                cachedCollection[key] = data;
                subscriber.callback(cachedCollection);
                continue;
            }

            subscriber.callback(data, key);
            continue;
        }

@marcaaron @roryabraham For non-collection also we are updating with a null value, so do we need to avoid that also?