Skip to content

Always use immediate parent report when rendering navigation header#37436

Merged
cristipaval merged 6 commits into
Expensify:mainfrom
barros001:fix/37111
Feb 28, 2024
Merged

Always use immediate parent report when rendering navigation header#37436
cristipaval merged 6 commits into
Expensify:mainfrom
barros001:fix/37111

Conversation

@barros001

@barros001 barros001 commented Feb 28, 2024

Copy link
Copy Markdown
Contributor

Details

Fixed Issues

$ #37111
PROPOSAL: #37111 (comment)

Tests

Test 1

  1. Post "message 1" on a room
  2. Click reply in thread on message 1
  3. Post "message 2"
  4. Click reply in thread on message 2
  5. Verify that the header shows "message 2" as the main navigation title and "message 1" as the parent navigation item.

Test 2

  1. Request money
  2. Open the created money request
  3. Send a message inside of it
  4. Click "Reply in thread" on the message
  5. Verify that the header shows the money request name in the navigation subtitle (and the workspace name if the request was made to the workspace).
  • Verify that no errors appear in the JS console

Offline tests

Test 1

  1. Post "message 1" on a room
  2. Click reply in thread on message 1
  3. Post "message 2"
  4. Click reply in thread on message 2
  5. Verify that the header shows "message 2" as the main navigation title and "message 1" as the parent navigation item.

Test 2

  1. Request money
  2. Open the created money request
  3. Send a message inside of it
  4. Click "Reply in thread" on the message
  5. Verify that the header shows the money request name in the navigation subtitle (and the workspace name if the request was made to the workspace).

QA Steps

Test 1

  1. Post "message 1" on a room
  2. Click reply in thread on message 1
  3. Post "message 2"
  4. Click reply in thread on message 2
  5. Verify that the header shows "message 2" as the main navigation title and "message 1" as the parent navigation item.

Test 2

  1. Request money
  2. Open the created money request
  3. Send a message inside of it
  4. Click "Reply in thread" on the message
  5. Verify that the header shows the money request name in the navigation subtitle (and the workspace name if the request was made to the workspace).
  • Verify that no errors appear in the JS console

PR Author Checklist

  • I linked the correct issue in the ### Fixed Issues section above
  • I wrote clear testing steps that cover the changes made in this PR
    • I added steps for local testing in the Tests section
    • I added steps for the expected offline behavior in the Offline steps section
    • I added steps for Staging and/or Production testing in the QA steps section
    • I added steps to cover failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
    • I tested this PR with a High Traffic account against the staging or production API to ensure there are no regressions (e.g. long loading states that impact usability).
  • I included screenshots or videos for tests on all platforms
  • I ran the tests on all platforms & verified they passed on:
    • Android: Native
    • Android: mWeb Chrome
    • iOS: Native
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
    • MacOS: Desktop
  • I verified there are no console errors (if there's a console error not related to the PR, report it or open an issue for it to be fixed)
  • I followed proper code patterns (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick)
    • I verified that the left part of a conditional rendering a React component is a boolean and NOT a string, e.g. myBool && <MyComponent />.
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text shown in the product is localized by adding it to src/languages/* files and using the translation method
      • If any non-english text was added/modified, I verified the translation was requested/reviewed in #expensify-open-source and it was approved by an internal Expensify engineer. Link to Slack message:
    • I verified all numbers, amounts, dates and phone numbers shown in the product are using the localization methods
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is approved by marketing by adding the Waiting for Copy label for a copy review on the original GH to get the correct copy.
    • I verified proper file naming conventions were followed for any new files or renamed files. All non-platform specific files are named after what they export and are not named "index.js". All platform-specific files are named for the platform the code supports as outlined in the README.
    • I verified the JSDocs style guidelines (in STYLE.md) were followed
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I followed the guidelines as stated in the Review Guidelines
  • I tested other components that can be impacted by my changes (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar are working as expected)
  • I verified all code is DRY (the PR doesn't include any logic written more than once, with the exception of tests)
  • I verified any variables that can be defined as constants (ie. in CONST.js or at the top of the file that uses the constant) are defined as such
  • I verified that if a function's arguments changed that all usages have also been updated correctly
  • If any new file was added I verified that:
    • The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG))
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label so the design team can review the changes.
  • If a new page is added, I verified it's using the ScrollView component to make it scrollable when more elements are added to the page.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.

Screenshots/Videos

Android: Native
android.webm
Android: mWeb Chrome
android.web.webm
iOS: Native
ios.mp4
iOS: mWeb Safari
ios.web.mp4
MacOS: Chrome / Safari
web.mov
MacOS: Desktop
desktop.mov

@barros001 barros001 marked this pull request as ready for review February 28, 2024 16:23
@barros001 barros001 requested a review from a team as a code owner February 28, 2024 16:23
@melvin-bot melvin-bot Bot requested review from paultsimura and removed request for a team February 28, 2024 16:23
@melvin-bot

melvin-bot Bot commented Feb 28, 2024

Copy link
Copy Markdown

@paultsimura Please copy/paste the Reviewer Checklist from here into a new comment on this PR and complete it. If you have the K2 extension, you can simply click: [this button]

@barros001

Copy link
Copy Markdown
Contributor Author

PR still missing Android native video (still building the app, it takes a very long time for me, I'll add it once build finishes).

One comment, I opted to rename the rootReportName property from ParentNavigationSummaryParams type now that we no longer go after the root report name but rather just the immediate parent. I didn't make sense to keep the name.

@barros001

Copy link
Copy Markdown
Contributor Author

Android video uploaded

@paultsimura

Copy link
Copy Markdown
Contributor

Thanks @barros001, I'm reviewing it now. In the meantime, could you please add another set of test steps to the PR description?

Test 1

  1. Post "message 1" on a room
  2. Click reply in thread on message 1
  3. Post "message 2"
  4. Click reply in thread on message 2
  5. Verify that the header shows "message 2" as the main navigation title and "message 1" as the parent navigation item.

Test 2

  1. Request money
  2. Open the created money request
  3. Send a message inside of it
  4. Click "Reply in thread" on the message
  5. Verify that the header shows the money request name in the navigation subtitle (and the workspace name if the request was made to the workspace).

@barros001

Copy link
Copy Markdown
Contributor Author

@paultsimura good point, I added the test scenario (and also performed it myself to verify it works, and it does).

@paultsimura

paultsimura commented Feb 28, 2024

Copy link
Copy Markdown
Contributor

Hey @barros001, here's a bold suggestion, but after digging deeper into the code and testing, I think we can just go with this (we may add one more check to address a bug I'll post in a separate comment):

function getParentNavigationSubtitle(report: OnyxEntry<Report>): ParentNavigationSummaryParams {
    const parentReport = getParentReport(report);
    if (isEmptyObject(parentReport)) {
        return {};
    }

    return {
        reportName: getReportName(parentReport),
        workspaceName: getPolicyName(parentReport, true),
    };
}

Could you please test if you could find any broken behavior if we go this way?

@paultsimura

Copy link
Copy Markdown
Contributor

While testing this PR, I've noticed another bug (not related to the PR, reproducible in prod):

If you, as an admin of the workspace, open an expense report of a member, it shows From {member_name} in {workspace}, which makes sense:

image

However, if you open the same expense report as a member, it shows From {workspace} in {workspace}:
image

Should we hide the in {workspace} part in this case? I believe this was an initial design, but it either wasn't implemented correctly or broken at some point.

cc: @quinthar @chiragsalian

@barros001

Copy link
Copy Markdown
Contributor Author

However, if you open the same expense report as a member, it shows From {workspace} in {workspace}:

It appears that this happens every time you go one step deep from a Workspace. Go to any workspace, send a message and then click reply in thread. You will see "From Workspace in Workspace".

Looks like we need an extra check if the parent report is a workspace, if that's the case then we do not return the workspace name. That sounds simple, I'll check it out.

As far as your bold suggestion, I'm still looking but that makes me a little nervous because I'm not sure how to test all scenarios. For example, how do I navigate to a report where isIOURequest would return true? What about isExpenseRequest? For isExpenseRequest, why would we ever return FAKE if it's also a isIOUReport? I'm trying to go into each one of these scenarios but I can't seem to find them (maybe that answers my own question, they are no longer valid scenarios?).

@paultsimura

paultsimura commented Feb 28, 2024

Copy link
Copy Markdown
Contributor

It appears that this happens every time you go one step deep from a Workspace. Go to any workspace, send a message and then click reply in thread. You will see "From Workspace in Workspace".

That's a valuable addition, thanks. I've tested different scenarios – this bug seems complicated enough to deserve a separate issue IMO. So we'll proceed only with the current scope.

@paultsimura

Copy link
Copy Markdown
Contributor

why would we ever return FAKE if it's also a isIOUReport

I can't think of a scenario where this is the case: a report cannot be both isExpenseRequest (a Transaction thread) and isIOUReport (a collection of 1:1 money requests) at the same time.

Moreover, if we return FAKE (I just messed with the code a bit), it'll show the Fake in the header:
image

Therefore, I think it's a dead code and we are safe to proceed with the change I suggested. I'll make sure to thoroughly test different cases.

@barros001

barros001 commented Feb 28, 2024

Copy link
Copy Markdown
Contributor Author

It appears that this happens every time you go one step deep from a Workspace. Go to any workspace, send a message and then click reply in thread. You will see "From Workspace in Workspace".
Looks like we need an extra check if the parent report is a workspace, if that's the case then we do not return the workspace name. That sounds simple, I'll check it out.

I've tested different scenarios – this bug seems quite complicated and deserves a separate issue IMO. So we'll proceed only with the current scope.

Agreed! As I had already spent some time digging, here's what I found, for future reference:

  1. There's not simple way to tell a report is in fact a workspace, at least not that I could find. No easy report._some_property_ that would tell you that.
  2. By inspect objects as I navigate and looking at code, looks like a workspace can be identified as a report that:
    1. Is not a thread;
    2. is a PolicyExpenseChat; and
    3. report?.isOwnPolicyExpenseChat is true.

With that, here's what I tried:

Added the following function:

function isWorkspace(report: OnyxEntry<Report>): boolean {
    return !isThread(report) && isPolicyExpenseChat(report) && !!report?.isOwnPolicyExpenseChat;
}

Then updated the logic to check if it's a workspace, as follows:

    if (isWorkspace(parentReport) || isIOURequest(parentReport)) {
        return {
            reportName: getReportName(parentReport),
        };
    }

I tested the scenarios I could think of and it seems to work, including the one when an admin is viewing an expense report sent by another user (here's why report?.isOwnPolicyExpenseChat was necessary).

With all this said, this all looks very complicated and error prone. There's no simple way to tell things apart and there's way too much logic all over the codebase that it's hard to make assumptions (hence my hesitation of making the method short as you suggested). I'm always down to simplify things, make code better, but in this current situation I'm leaning towards staying on the pragmatism camp.

@paultsimura

Copy link
Copy Markdown
Contributor

With all this said, this all looks very complicated and error prone. There's no simple way to tell things apart and there's way too much logic all over the codebase that it's hard to make assumptions (hence my hesitation of making the method short as you suggested). I'm always down to simplify things, make code better, but in this current situation I'm leaning towards staying on the pragmatism camp.

I understand your concerns, but from my tests, there is no error in the suggested simplified approach.
Let's just make a deal then: if a regression arises that wouldn't have happened without this simplification, I'll compensate your penalty from my share. Would that work?

@barros001

Copy link
Copy Markdown
Contributor Author

why would we ever return FAKE if it's also a isIOUReport

I can't think of a scenario where this is the case: a report cannot be both isExpenseRequest (a Transaction thread) and isIOUReport (a collection of 1:1 money requests) at the same time.

Moreover, if we return FAKE (I just messed with the code a bit), it'll show the Fake in the header: image

Therefore, I think it's a dead code and we are safe to proceed with the change I suggested. I'll make sure to thoroughly test different cases.

Ok, I agree with you here, if we remove the FAKE condition then we can safely remove the whole if (isExpenseRequest(parentReport)) block as it would return exactly the same as the default response. I'll kill that block and push it.

We're not left with (just trying to slow down a bit to make sure I fully understand this):

    if (isIOURequest(parentReport)) {
        return {
            reportName: getReportName(parentReport),
        };
    }

In this case we would not return the workspace, and I wonder if this is related to the new bug you found where we don't want to return the workspace name when reportName is already the workspace name. I just could not find a scenario where that condition would be true though, did you?

@paultsimura

paultsimura commented Feb 28, 2024

Copy link
Copy Markdown
Contributor

Reviewer Checklist

  • I have verified the author checklist is complete (all boxes are checked off).
  • I verified the correct issue is linked in the ### Fixed Issues section above
  • I verified testing steps are clear and they cover the changes made in this PR
    • I verified the steps for local testing are in the Tests section
    • I verified the steps for Staging and/or Production testing are in the QA steps section
    • I verified the steps cover any possible failure scenarios (i.e. verify an input displays the correct error message if the entered data is not correct)
    • I turned off my network connection and tested it while offline to ensure it matches the expected behavior (i.e. verify the default avatar icon is displayed if app is offline)
  • I checked that screenshots or videos are included for tests on all platforms
  • I included screenshots or videos for tests on all platforms
  • I verified tests pass on all platforms & I tested again on:
    • Android: Native
    • Android: mWeb Chrome
    • iOS: Native
    • iOS: mWeb Safari
    • MacOS: Chrome / Safari
    • MacOS: Desktop
  • If there are any errors in the console that are unrelated to this PR, I either fixed them (preferred) or linked to where I reported them in Slack
  • I verified proper code patterns were followed (see Reviewing the code)
    • I verified that any callback methods that were added or modified are named for what the method does and never what callback they handle (i.e. toggleReport and not onIconClick).
    • I verified that the left part of a conditional rendering a React component is a boolean and NOT a string, e.g. myBool && <MyComponent />.
    • I verified that comments were added to code that is not self explanatory
    • I verified that any new or modified comments were clear, correct English, and explained "why" the code was doing something instead of only explaining "what" the code was doing.
    • I verified any copy / text shown in the product is localized by adding it to src/languages/* files and using the translation method
    • I verified all numbers, amounts, dates and phone numbers shown in the product are using the localization methods
    • I verified any copy / text that was added to the app is grammatically correct in English. It adheres to proper capitalization guidelines (note: only the first word of header/labels should be capitalized), and is approved by marketing by adding the Waiting for Copy label for a copy review on the original GH to get the correct copy.
    • I verified proper file naming conventions were followed for any new files or renamed files. All non-platform specific files are named after what they export and are not named "index.js". All platform-specific files are named for the platform the code supports as outlined in the README.
    • I verified the JSDocs style guidelines (in STYLE.md) were followed
  • If a new code pattern is added I verified it was agreed to be used by multiple Expensify engineers
  • I verified that this PR follows the guidelines as stated in the Review Guidelines
  • I verified other components that can be impacted by these changes have been tested, and I retested again (i.e. if the PR modifies a shared library or component like Avatar, I verified the components using Avatar have been tested & I retested again)
  • I verified all code is DRY (the PR doesn't include any logic written more than once, with the exception of tests)
  • I verified any variables that can be defined as constants (ie. in CONST.js or at the top of the file that uses the constant) are defined as such
  • If a new component is created I verified that:
    • A similar component doesn't exist in the codebase
    • All props are defined accurately and each prop has a /** comment above it */
    • The file is named correctly
    • The component has a clear name that is non-ambiguous and the purpose of the component can be inferred from the name alone
    • The only data being stored in the state is data necessary for rendering and nothing else
    • For Class Components, any internal methods passed to components event handlers are bound to this properly so there are no scoping issues (i.e. for onClick={this.submit} the method this.submit should be bound to this in the constructor)
    • Any internal methods bound to this are necessary to be bound (i.e. avoid this.submit = this.submit.bind(this); if this.submit is never passed to a component event handler like onClick)
    • All JSX used for rendering exists in the render method
    • The component has the minimum amount of code necessary for its purpose, and it is broken down into smaller components in order to separate concerns and functions
  • If any new file was added I verified that:
    • The file has a description of what it does and/or why is needed at the top of the file if the code is not self explanatory
  • If a new CSS style is added I verified that:
    • A similar style doesn't already exist
    • The style can't be created with an existing StyleUtils function (i.e. StyleUtils.getBackgroundAndBorderStyle(theme.componentBG)
  • If the PR modifies code that runs when editing or sending messages, I tested and verified there is no unexpected behavior for all supported markdown - URLs, single line code, code blocks, quotes, headings, bold, strikethrough, and italic.
  • If the PR modifies a generic component, I tested and verified that those changes do not break usages of that component in the rest of the App (i.e. if a shared library or component like Avatar is modified, I verified that Avatar is working as expected in all cases)
  • If the PR modifies a component related to any of the existing Storybook stories, I tested and verified all stories for that component are still working as expected.
  • If the PR modifies a component or page that can be accessed by a direct deeplink, I verified that the code functions as expected when the deeplink is used - from a logged in and logged out account.
  • If the PR modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label so the design team can review the changes.
  • If a new page is added, I verified it's using the ScrollView component to make it scrollable when more elements are added to the page.
  • If the main branch was merged into this PR after a review, I tested again and verified the outcome was still expected according to the Test steps.
  • I have checked off every checkbox in the PR reviewer checklist, including those that don't apply to this PR.

Screenshots/Videos

Android: Native
android-compressed.mp4
Android: mWeb Chrome
chrome-compressed.mp4
iOS: Native
Simulator.Screen.Recording.-.iPhone.15.Pro.Max.-.2024-02-28.at.21.53.27-compressed.mp4
iOS: mWeb Safari
Simulator.Screen.Recording.-.iPhone.15.Pro.Max.-.2024-02-28.at.21.51.47-compressed.mp4
MacOS: Chrome / Safari
Screen.Recording.2024-02-28.at.20.42.43-compressed.mp4
MacOS: Desktop
Screen.Recording.2024-02-28.at.21.36.59-compressed.mp4

@barros001

barros001 commented Feb 28, 2024

Copy link
Copy Markdown
Contributor Author

@paultsimura I just pushed a simplified version. I still have the if (isIOURequest(parentReport)) { block in there, I just want to look a little bit further so that I gain a better understanding of that.

But I do trust you when you say you tested and didn't find any errors, you have a whole lot more experience with the codebase than I do. I'll push an update shortly.

@paultsimura

Copy link
Copy Markdown
Contributor

No, hold on please. I'll test a few more cases that look suspicious 🤔

@barros001

barros001 commented Feb 28, 2024

Copy link
Copy Markdown
Contributor Author

@paultsimura alright, I found where if (isIOURequest(parentReport)) { is used, and maybe we should really keep it there (or maybe not!)

Here are the steps:

  1. Account 1, click + then SEND MONEY
  2. Select Account 2 and click Pay Elsewhere
  3. On Account 2, navigate to the new report for the payment that shows up on LHN
  4. Click on the payment report
  5. Click AGAIN on the payment report
  6. Send a message
  7. Click reply in a thread

That's when that block is true.

Here's a screenshot without that if block.

image

Notice the header where it says in carlos+1's Expenses. That if block was meant to remove that part.

Here's a screenshot of how it currently is:

image

So that if block removes that in carlos+1's Expenses from the header.

Thoughts?

@paultsimura

Copy link
Copy Markdown
Contributor

About the thing with the isIOURequest:

An IOU request is a 1:1 money request (not a request from a workspace). Therefore, there should not be a policy related to the parent report (except for the personal expense policy, which is currently shown in the subtitle in production).

I did notice the same thing you've mentioned here, therefore I asked to hold on. But if you check production – this check has no effect, and the in carlos+1's Expenses is not cut off there.

If we leave this check, we'll basically introduce a new inconsistent behavior (e.g. if you create a thread inside that thread, the in carlos+1's Expenses will be visible in that 2nd level thread again).

So I would say we should remove this check as well (so the workspace name is consistent with the production) and if we consider it as a but – we should address it separately along with the From {workspace} in {workspace} bug as they're more or less related.

@barros001

Copy link
Copy Markdown
Contributor Author

About the thing with the isIOURequest:

An IOU request is a 1:1 money request (not a request from a workspace). Therefore, there should not be a policy related to the parent report (except for the personal expense policy, which is currently shown in the subtitle in production).

I did notice the same thing you've mentioned here, therefore I asked to hold on. But if you check production – this check has no effect, and the in carlos+1's Expenses is not cut off there.

If we leave this check, we'll basically introduce a new inconsistent behavior (e.g. if you create a thread inside that thread, the in carlos+1's Expenses will be visible in that 2nd level thread again).

So I would say we should remove this check as well (so the workspace name is consistent with the production) and if we consider it as a but – we should address it separately along with the From {workspace} in {workspace} bug as they're more or less related.

Came here to say pretty much the same. I just noticed that having the in Carlos+1's Expenses is actually the correct behavior. It's added there by the recursive call back to the root. Removing recursion would remove this part from the header, creating a new bug. That's because of the if (isIOURequest(parentReport)) { block. Removing that as well makes it bahave as expected.

I'll go ahead and push a new version.

@barros001

Copy link
Copy Markdown
Contributor Author

Done, new version pushed. Phew! That was a tricky one. I appreciate the thorough review and feedback.

@paultsimura paultsimura left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@barros001 thanks for keeping the discussion here fruitful 💪

@melvin-bot melvin-bot Bot requested a review from cristipaval February 28, 2024 20:56
@cristipaval cristipaval merged commit 4cd33a3 into Expensify:main Feb 28, 2024
@OSBotify

Copy link
Copy Markdown
Contributor

✋ This PR was not deployed to staging yet because QA is ongoing. It will be automatically deployed to staging after the next production release.

@OSBotify

Copy link
Copy Markdown
Contributor

🚀 Deployed to staging by https://github.com/cristipaval in version: 1.4.45-0 🚀

platform result
🤖 android 🤖 success ✅
🖥 desktop 🖥 success ✅
🍎 iOS 🍎 success ✅
🕸 web 🕸 success ✅

@OSBotify

Copy link
Copy Markdown
Contributor

🚀 Deployed to production by https://github.com/puneetlath in version: 1.4.45-6 🚀

platform result
🤖 android 🤖 success ✅
🖥 desktop 🖥 success ✅
🍎 iOS 🍎 success ✅
🕸 web 🕸 success ✅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants