Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[$500] Expense - Receipts in the expense preview do not update after adding/deleting receipt #33550

Closed
4 of 6 tasks
lanitochka17 opened this issue Dec 23, 2023 · 43 comments
Closed
4 of 6 tasks
Assignees
Labels
Bug Something is broken. Auto assigns a BugZero manager. Daily KSv2 Engineering Internal Requires API changes or must be handled by Expensify staff

Comments

@lanitochka17
Copy link

lanitochka17 commented Dec 23, 2023

If you haven’t already, check out our contributing guidelines for onboarding and email [email protected] to request to join our Slack channel!


Version Number: 1.4.16-4
Reproducible in staging?: Y
Reproducible in production?: Y
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
Expensify/Expensify Issue URL:
Issue reported by: Applause - Internal Team
Slack conversation:

Action Performed:

Precondition: There are more than 3 requests in the workspace chat and 3 of them have a receipt

  1. Navigate to the workspace chat
  2. Tap on expense preview
  3. Tap on any request
  4. Add a receipt to the request
  5. Tap back button on the top left twice

Expected Result:

The receipts in the expense preview in the workspace chat will update and +1 counter will show

Actual Result:

The receipts in the expense preview in workspace chat does not update. It only shows +1 counter after refreshing the page

Workaround:

Unknown

Platforms:

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

  • Android: Native
  • Android: mWeb Chrome
  • iOS: Native
  • iOS: mWeb Safari
  • MacOS: Chrome / Safari
  • MacOS: Desktop

Screenshots/Videos

Add any screenshot/video evidence

Bug6324986_1703347995567.1000005049.1.mp4

View all open jobs on GitHub

Upwork Automation - Do Not Edit
  • Upwork Job URL: https://www.upwork.com/jobs/~0198c10fd73498b28a
  • Upwork Job ID: 1738598664125079552
  • Last Price Increase: 2023-12-30
  • Automatic offers:
    • cubuspl42 | Reviewer | 28102438
    • bernhardoj | Contributor | 28102439
@lanitochka17 lanitochka17 added External Added to denote the issue can be worked on by a contributor Daily KSv2 Bug Something is broken. Auto assigns a BugZero manager. labels Dec 23, 2023
@melvin-bot melvin-bot bot changed the title Expense - Receipts in the expense preview do not update after adding/deleting receipt [$500] Expense - Receipts in the expense preview do not update after adding/deleting receipt Dec 23, 2023
Copy link

melvin-bot bot commented Dec 23, 2023

Job added to Upwork: https://www.upwork.com/jobs/~0198c10fd73498b28a

@melvin-bot melvin-bot bot added the Help Wanted Apply this label when an issue is open to proposals by contributors label Dec 23, 2023
Copy link

melvin-bot bot commented Dec 23, 2023

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

Copy link

melvin-bot bot commented Dec 23, 2023

Bug0 Triage Checklist (Main S/O)

  • This "bug" occurs on a supported platform (ensure Platforms in OP are ✅)
  • This bug is not a duplicate report (check E/App issues and #expensify-bugs)
    • If it is, comment with a link to the original report, close the issue and add any novel details to the original issue instead
  • This bug is reproducible using the reproduction steps in the OP. S/O
    • If the reproduction steps are clear and you're unable to reproduce the bug, check with the reporter and QA first, then close the issue.
    • If the reproduction steps aren't clear and you determine the correct steps, please update the OP.
  • This issue is filled out as thoroughly and clearly as possible
    • Pay special attention to the title, results, platforms where the bug occurs, and if the bug happens on staging/production.
  • I have reviewed and subscribed to the linked Slack conversation to ensure Slack/Github stay in sync

Copy link

melvin-bot bot commented Dec 23, 2023

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

@bernhardoj
Copy link
Contributor

bernhardoj commented Dec 23, 2023

Proposal

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

The number of transactions with receipts on the report preview doesn't update.

What is the root cause of that problem?

The transaction with a receipt is calculated here.

const transactionsWithReceipts = ReportUtils.getTransactionsWithReceipts(props.iouReportID);

In getTransactionWithReceipts, we get all transactions that are linked with the passed IOU report id.

App/src/libs/ReportUtils.ts

Lines 1898 to 1901 in b74be72

function getTransactionsWithReceipts(iouReportID: string | undefined): Transaction[] {
const transactions = TransactionUtils.getAllReportTransactions(iouReportID);
return transactions.filter((transaction) => TransactionUtils.hasReceipt(transaction));
}

However, it won't rerender when the transaction collection is updated (added/removed) because we aren't subscribing the component with the transaction collection. Instead, we use a different technique here by using onyxSubscribe.

useEffect(() => {
const unsubscribeOnyxTransaction = onyxSubscribe({
key: ONYXKEYS.COLLECTION.TRANSACTION,
waitForCollectionCallback: true,
callback: (allTransactions) => {
if (_.isEmpty(allTransactions)) {
return;
}
sethasMissingSmartscanFields(ReportUtils.hasMissingSmartscanFields(props.iouReportID));
setAreAllRequestsBeingSmartScanned(ReportUtils.areAllRequestsBeingSmartScanned(props.iouReportID, props.action));
setHasOnlyDistanceRequests(ReportUtils.hasOnlyDistanceRequestTransactions(props.iouReportID));
setHasNonReimbursableTransactions(ReportUtils.hasNonReimbursableTransactions(props.iouReportID));
},
});
return () => {
unsubscribeOnyxTransaction();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

(here is the reason why we do this)

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

If we want to keep the onyxSubscribe, then we should change transactionsWithReceipts to a state and update it inside onyxSubscribe.

const [transactionsWithReceipts, setTransactionsWithReceipts] = useState(ReportUtils.getTransactionsWithReceipts(props.iouReportID));

// inside onyxSubscribe
setTransactionsWithReceipts(ReportUtils.getTransactionsWithReceipts(props.iouReportID))

What alternative solutions did you explore? (Optional)

Otherwise, we can subscribe to the whole collection and use a selector to only select the transaction that is linked with the IOU report id. We will need to make a lot of changes to the code too, for example, we need to pass the transaction array to getTransactionsWithReceipts instead of the iou report id (we can technically keep it as it is).

@tienifr
Copy link
Contributor

tienifr commented Dec 25, 2023

Proposal

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

The receipts in the expense preview in workspace chat does not update. It only shows +1 counter after refreshing the page

What is the root cause of that problem?

We use transactionsWithReceipts to get the receipts here

Here's the detail implementation of getTransactionsWithReceipts, we get all transactions from onyx.connect

App/src/libs/ReportUtils.ts

Lines 1898 to 1901 in 7b836cf

function getTransactionsWithReceipts(iouReportID: string | undefined): Transaction[] {
const transactions = TransactionUtils.getAllReportTransactions(iouReportID);
return transactions.filter((transaction) => TransactionUtils.hasReceipt(transaction));
}

In theory, when the transactions get changed, transactions from Onyx.connect will be updated first, and then re-render ReportPreview so when re-calculating getTransactionsWithReceipts, we can get the updated transactions

But in ReportPreview, we just subscribed ONYXKEYS.COLLECTION.TRANSACTION in onyxSubscribe, so when transactions get changed, we did not update transactionsWithReceipts

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

We should cover all places that use data from Onyx.connect, we have iouSettled and transactionsWithReceipts

  • For iouSettled:

we're getting report from allReports but it works well for now, because we passed iouReport to ReportPreview so this component is re-rendered when iouReport get changed

  • For transactionsWithReceipts:

If we just create the new state for it and update in onyxSubscribe, we will break the advantage of onyxSubscribe, because transactionsWithReceipts is the array (not just boolean as hasMissingSmartscanFields, areAllRequestsBeingSmartScanned,...) and updating to new array will make the component re-render every times

To address this issue, we should create the new state for transactionsWithReceipts and just get the necessary properties from transactions. We just update transactionsWithReceipts in onyxSubscribe when it's really changed

const [transactionsWithReceipts, setTransactionsWithReceipts] = useState([]);
const transactionsWithReceiptsRef = useRef([])

...
 useEffect(() => { 
     const unsubscribeOnyxTransaction = onyxSubscribe({ 
         key: ONYXKEYS.COLLECTION.TRANSACTION, 
         waitForCollectionCallback: true, 
         callback: (allTransactions) => { 
            const newTransactionsWithReceipts = ReportUtils.getTransactionsWithReceipts(props.iouReportID).map(t=>({
receipt: t.receipt,
filename: t.filename,
pendingFields: t.pendingFields,
}))

     if(!_.isEqual(newTransactionsWithReceipts, transactionsWithReceiptsRef.current)){ // deep comparison
setTransactionsWithReceipts(newTransactionsWithReceipts)
transactionsWithReceiptsRef.current = newTransactionsWithReceipts
}

...

What alternative solutions did you explore? (Optional)

NA

@melvin-bot melvin-bot bot added the Overdue label Dec 26, 2023
Copy link

melvin-bot bot commented Dec 27, 2023

@cubuspl42, @Christinadobrzyn Whoops! This issue is 2 days overdue. Let's get this updated quick!

@cubuspl42
Copy link
Contributor

@bernhardoj @tienifr Would you consider adding to your GitHub profile...

  • an avatar, doesn't have to be an actual photo (helps to distinguish avatars for colorblind and partially colorblind people like me)
  • preferred pronouns

@melvin-bot melvin-bot bot removed the Overdue label Dec 27, 2023
@bernhardoj
Copy link
Contributor

@cubuspl42 sorry to hear that, I have added a profile picture

@cubuspl42
Copy link
Contributor

@tienifr

Can you use transactionsWithReceipts inside onyxSubscribe's callback anonymous function just like that?

The surrounding useEffect has empty deps ([]), there's no useCallback involved.

@tienifr
Copy link
Contributor

tienifr commented Dec 29, 2023

@cubuspl42 Ah, I mean using transactionsWithReceiptsRef, we can use the previous transactionsWithReceipts through ref in callback function. I updated my proposal to use ref as well

@cubuspl42
Copy link
Contributor

@tienifr

Now transactionsWithReceipts seems to be unused...

const [transactionsWithReceipts, setTransactionsWithReceipts] = useState([]);

Have you tested these changes? While I agree that it might be beneficial to give some focus to the performance here, the code must first and foremost work.

Would you share the branch with the code you used for testing that the proposed solution works as expected?

@tienifr
Copy link
Contributor

tienifr commented Dec 29, 2023

@cubuspl42 Here you are: https://github.com/tienifr/App/tree/fix/33550

Evidence video:

Screen.Recording.2023-12-29.at.17.24.25.mov

@cubuspl42
Copy link
Contributor

cubuspl42 commented Dec 29, 2023

Thank you!

I'm still in two minds with this one, though... While it's good that the proposal tries to focus on providing good performance, the exact solution feels somewhat hacky.

We're using both a ref and a state property to store transactionsWithReceipts. I know that this pattern is sometimes used in the React world, but it feels at least workaroundish and I wouldn't apply it unless really necessary.

@bernhardoj

Do you know what's the performance characteristic of your solution? How often would setTransactionsWithReceipts be called? Only once per specific user interaction (like adding/deleting a receipt), or tens/hundreds/thousands of times per second?

@bernhardoj
Copy link
Contributor

@cubuspl42 because we subscribe to the whole transaction, setTransactionsWithReceipts will be called whenever the collection is updated. It can be updated by:

  1. Add/edit/delete a money request
  2. Add/replace/remove a receipt
  3. etc.

I guess we can do something like this to prevent the re-render if the array contents are the same.

setTransactionsWithReceipts((prevTransactions) => {
    const newTransactions = ReportUtils.getTransactionsWithReceipts(props.iouReportID);
    return _.isEqual(prevTransactions, newTransactions) ? prevTransactions : newTransactions;
})

Talking about the performance, the current usage of onyxSubscribe is not really performant.

key: ONYXKEYS.COLLECTION.TRANSACTION,
waitForCollectionCallback: true,
callback: (allTransactions) => {
if (_.isEmpty(allTransactions)) {
return;
}
sethasMissingSmartscanFields(ReportUtils.hasMissingSmartscanFields(props.iouReportID));
setAreAllRequestsBeingSmartScanned(ReportUtils.areAllRequestsBeingSmartScanned(props.iouReportID, props.action));
setHasOnlyDistanceRequests(ReportUtils.hasOnlyDistanceRequestTransactions(props.iouReportID));
setHasNonReimbursableTransactions(ReportUtils.hasNonReimbursableTransactions(props.iouReportID));
},
});

Even though it doesn't cause a re-render, the four util functions make the same multiple loops to get all or linked transactions.

If we really want to improve the performance here, we can create like a wrapper for report preview like below. The 4 util functions above now only need to accept the transactions collection instead of the iou report ID.

function ReportPreviewWrapper(props) {
    const linkedTransactions = useMemo(() => Object.values(props.transactions).filter((transaction) => `${transaction.reportID}` === `${props.iouReportID}`), [props.transactions, props.iouReportID]);
    const transactionsWithReceipts = useMemo(() => linkedTransactions.filter((transaction) => TransactionUtils.hasReceipt(transaction)), [linkedTransactions]);
    const hasMissingSmartscanFields = ...
    const areAllRequestsBeingSmartScanned = ...
    const hasOnlyDistanceRequests = ...
    const hasNonReimbursableTransactions = ...

    return <ReportPreview {...props} transactionsWithReceipts={transactionsWithReceipts} ... />
}

export default withOnyx({
    transactions: {
        key: ONYXKEYS.COLLECTION.TRANSACTION,
    },
})(ReportPreviewWrapper);

And then memoized the ReportPreview.

memo(ReportPreview, (prevProps, nextProps) => _.isEqual(prevProps, nextProps))

Many things can be done to improve the performance, theoretically (because I didn't do any measurement).

Copy link

melvin-bot bot commented Dec 30, 2023

📣 It's been a week! Do we have any satisfactory proposals yet? Do we need to adjust the bounty for this issue? 💸

@melvin-bot melvin-bot bot added the Overdue label Jan 1, 2024
Copy link

melvin-bot bot commented Jan 1, 2024

@cubuspl42, @Christinadobrzyn Whoops! This issue is 2 days overdue. Let's get this updated quick!

Copy link

melvin-bot bot commented Jan 1, 2024

@cubuspl42, @Christinadobrzyn Uh oh! This issue is overdue by 2 days. Don't forget to update your issues!

This comment was marked as off-topic.

Copy link

melvin-bot bot commented Jan 11, 2024

@puneetlath, @cubuspl42, @Christinadobrzyn Uh oh! This issue is overdue by 2 days. Don't forget to update your issues!

@puneetlath
Copy link
Contributor

The proposal and the proposed compensation sounds good to me. Good by you @bernhardoj? If so, I'll assign you.

@melvin-bot melvin-bot bot removed the Overdue label Jan 12, 2024
@bernhardoj
Copy link
Contributor

@puneetlath I'm good with that

Copy link

melvin-bot bot commented Jan 13, 2024

@puneetlath @cubuspl42 @Christinadobrzyn this issue is now 3 weeks old. There is one more week left before this issue breaks WAQ and will need to go internal. What needs to happen to get a PR in review this week? Please create a thread in #expensify-open-source to discuss. Thanks!

@melvin-bot melvin-bot bot removed the Help Wanted Apply this label when an issue is open to proposals by contributors label Jan 15, 2024
Copy link

melvin-bot bot commented Jan 15, 2024

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

Offer link
Upwork job

Copy link

melvin-bot bot commented Jan 15, 2024

📣 @bernhardoj 🎉 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 melvin-bot bot removed the Overdue label Jan 15, 2024
@puneetlath
Copy link
Contributor

Assigning @bernhardoj. Agreed payment breakdown here: #33550 (comment)

@bernhardoj
Copy link
Contributor

Actually, I wasn't able to reproduce this anymore because in this PR, we connect to the whole transactions collection 😅

@cubuspl42
Copy link
Contributor

Oh my, so this was one of those that you can wait out...

@puneetlath
Copy link
Contributor

Ah, wow ok. So sounds like nothing to do here and we should just close the issue out?

Thanks for all the discussion though!

@melvin-bot melvin-bot bot added the Overdue label Jan 18, 2024
Copy link

melvin-bot bot commented Jan 20, 2024

@puneetlath @cubuspl42 @bernhardoj @Christinadobrzyn this issue is now 4 weeks old and preventing us from maintaining WAQ, can you:

  • Decide whether any proposals currently meet our guidelines and can be approved as-is today
  • If no proposals meet that standard, please take this issue internal and treat it as one of your highest priorities
  • If you have any questions, don't hesitate to start a discussion in #expensify-open-source

Thanks!

@melvin-bot melvin-bot bot added Internal Requires API changes or must be handled by Expensify staff and removed External Added to denote the issue can be worked on by a contributor labels Jan 20, 2024
Copy link

melvin-bot bot commented Jan 20, 2024

Current assignee @cubuspl42 is eligible for the Internal assigner, not assigning anyone new.

@cubuspl42
Copy link
Contributor

Internal Slack thread

@melvin-bot melvin-bot bot removed the Overdue label Jan 22, 2024
@Christinadobrzyn
Copy link
Contributor

@cubuspl42 can you let us know what you think is fair for the partial payment?

@Christinadobrzyn
Copy link
Contributor

Based on this Slack convo - https://expensify.slack.com/archives/C02NK2DQWUX/p1705917174041169

I paid out @cubuspl42 $125 (25% of $500) for the job. The payment is through Upwork.

Feel free to reach out with any questions!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Bug Something is broken. Auto assigns a BugZero manager. Daily KSv2 Engineering Internal Requires API changes or must be handled by Expensify staff
Projects
No open projects
Development

No branches or pull requests

6 participants