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

Search - Highlight newly added expense #49192

Merged
merged 20 commits into from
Sep 30, 2024

Conversation

ikevin127
Copy link
Contributor

Details

Fixed Issues

$ #48716
PROPOSAL:

Tests

  1. Switch to the Search page tab.
  2. Click the global create button (FAB) > Submit expense > Submit an expense.
  3. Verify you remain on the search page.
  4. Verify that the newly added expense appears in the table and the row is briefly highlighted to draw attention to it.
  • Verify that no errors appear in the JS console

Offline tests

  1. Switch to the Search page tab.
  2. Go offline.
  3. Click the global create button (FAB) > Submit expense > Submit an expense.
  4. Verify you remain on the search page.
  5. Go back online.
  6. Verify that the newly added expense appears in the table and the row is briefly highlighted to draw attention to it.

QA Steps

TLDR: Same as Tests.

  1. Switch to the Search page tab.
  2. Click the global create button (FAB) > Submit expense > Submit an expense.
  3. Verify you remain on the search page.
  4. Verify that the newly added expense appears in the table and the row is briefly highlighted to draw attention to it.
  • 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 either coming verbatim from figma or has been approved by marketing (in order to get marketing approval, ask the Bug Zero team member to add the Waiting for copy label to the issue)
    • 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 UI (e.g. new buttons, new UI components, changing the padding/spacing/sizing, moving components, etc) or modifies the form input styles:
    • I verified that all the inputs inside a form are aligned with each other.
    • I added Design label and/or tagged @Expensify/design 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-mweb.webm
iOS: Native
ios.mp4
iOS: mWeb Safari
ios-mweb.mp4
MacOS: Chrome / Safari
web.mov
MacOS: Desktop
desktop.mov

@ikevin127 ikevin127 requested a review from a team as a code owner September 13, 2024 21:32
@melvin-bot melvin-bot bot requested review from dubielzyk-expensify and ishpaul777 and removed request for a team September 13, 2024 21:32
Copy link

melvin-bot bot commented Sep 13, 2024

@dubielzyk-expensify @ishpaul777 One of you needs to 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]

Comment on lines 138 to 166
const searchTriggeredRef = useRef(false);

useEffect(() => {
const previousTransactionsLength = previousTransactions && Object.keys(previousTransactions).length;
const transactionsLength = transactions && Object.keys(transactions).length;

if (!previousTransactionsLength || !transactionsLength || previousTransactionsLength === transactionsLength) {
return;
}

// Check if the search block was already triggered
if (!searchTriggeredRef.current && transactionsLength > previousTransactionsLength) {
// A transaction was added, trigger the action again
SearchActions.search({queryJSON, offset});
searchTriggeredRef.current = true; // Set the flag to true to prevent further triggers
}
}, [transactions, previousTransactions, queryJSON, offset]);

// Reset the flag using InteractionManager after the search action
useEffect(() => {
if (!searchTriggeredRef.current) {
return;
}

// Schedule a task to reset the flag after all current interactions have completed
const interactionHandle = InteractionManager.runAfterInteractions(() => {
searchTriggeredRef.current = false; // Reset the flag after interactions
});

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@luacmartins Ideally, we would update the Search snapshot in BE and pop the newly added transaction in the search list via pusher and we wouldn't have to check transactions / previous transactions in this way - but it's better than performing a full page reload from a UX point of view, given that we want to pop the row in with a nice animation.

@ishpaul777
Copy link
Contributor

this needs a c+, right ? @ikevin127

@ikevin127
Copy link
Contributor Author

@ishpaul777 I assume it does, coming from the issue - currently we're still polishing the animation and I have to fix the failed checks. Will let you know once this is ready for C+ review 👍

@ishpaul777
Copy link
Contributor

Nice, thanks for clarifying 🤗 let me know if i can help anyways..

@luacmartins luacmartins self-requested a review September 13, 2024 21:49
@ikevin127
Copy link
Contributor Author

ikevin127 commented Sep 14, 2024

@luacmartins Not sure why the tests are failing, could it be because of the FE implementation mentioned in #49192 ? 🤔

Unit tests (job 1)

Summary of all failing tests
FAIL tests/actions/OnyxUpdateManagerTest.ts

● Test suite failed to run
 Jest worker encountered 4 child process exceptions, exceeding retry limit
at ChildProcessWorker.initialize (node_modules/jest-runner/node_modules/jest-worker/build/workers/ChildProcessWorker.js:181:21)

Test Suites: 1 failed, 30 passed, 31 total
Tests:       307 passed, 307 total
Snapshots:   0 total
Time:        62.075 s, estimated 85 s

Unit tests (job 2)

Summary of all failing tests
FAIL tests/actions/ReportTest.ts (32.242 s)  
  ● actions/Report › should show a notification for report action updates with shouldNotify  

    expect(jest.fn()).toBeCalledWith(...expected)  

    Expected: "1", {"actionName": "ADDCOMMENT"}  

    Number of calls: 0  

      549 | .then(() => {  
      550 | // Ensure we show a notification for this new report action  
    > 551 | expect(Report.showReportActionNotification).toBeCalledWith(REPORT_ID, REPORT_ACTION);  
      552 | })  
      553 |  

      at toBeCalledWith (tests/actions/ReportTest.ts:551:61)  

FAIL tests/unit/OnyxUpdateManagerTest.ts  
  ● OnyxUpdateManager › should fetch missing Onyx updates once, defer updates and apply after missing updates  

    expect(jest.fn()).toHaveBeenCalledTimes(expected)  

    Expected number of calls: 1  
    Received number of calls: 0  

      53 |  
      54 | // There are no gaps in the deferred updates, therefore only one call to getMissingOnyxUpdates should be triggered  
    > 55 | expect(App.getMissingOnyxUpdates).toHaveBeenCalledTimes(1);  
      56 | expect(ApplyUpdates.applyUpdates).toHaveBeenCalledTimes(1);  
      57 |  
      58 | // validateAndApplyDeferredUpdates should be called twice, once for the initial deferred updates and once for the remaining deferred updates with gaps.  

      at toHaveBeenCalledTimes (tests/unit/OnyxUpdateManagerTest.ts:55:47)  

  ● OnyxUpdateManager › should only apply deferred updates that are newer than the last locally applied update (pending updates)  

    expect(jest.fn()).toHaveBeenCalledTimes(expected)  

    Expected number of calls: 1  
    Received number of calls: 0  

      87 |  
      88 | // There are no gaps in the deferred updates, therefore only one call to getMissingOnyxUpdates should be triggered  
    > 89 | expect(App.getMissingOnyxUpdates).toHaveBeenCalledTimes(1);  
      90 | expect(ApplyUpdates.applyUpdates).toHaveBeenCalledTimes(1);  
      91 |  
      92 | // validateAndApplyDeferredUpdates should be called twice, once for the initial deferred updates and once for the remaining deferred updates with gaps.  

      at toHaveBeenCalledTimes (tests/unit/OnyxUpdateManagerTest.ts:89:47)  

  ● OnyxUpdateManager › should re-fetch missing updates if the deferred updates have a gap  

    expect(jest.fn()).toHaveBeenCalledTimes(expected)  

    Expected number of calls: 2  
    Received number of calls: 0  

      117 |  
      118 | // Even though there is a gap in the deferred updates, we only want to fetch missing updates once per batch.  
    > 119 | expect(App.getMissingOnyxUpdates).toHaveBeenCalledTimes(2);  
      120 | expect(ApplyUpdates.applyUpdates).toHaveBeenCalledTimes(2);  
      121 |  
      122 | // validateAndApplyDeferredUpdates should be called twice, once for the initial deferred updates and once for the remaining deferred updates with gaps.  

      at toHaveBeenCalledTimes (tests/unit/OnyxUpdateManagerTest.ts:119:47)  

  ● OnyxUpdateManager › should re-fetch missing deferred updates only once per batch  

    expect(jest.fn()).toHaveBeenCalledTimes(expected)  

    Expected number of calls: 2  
    Received number of calls: 0  

      153 |  
      154 | // Even though there are multiple gaps in the deferred updates, we only want to fetch missing updates once per batch.  
    > 155 | expect(App.getMissingOnyxUpdates).toHaveBeenCalledTimes(2);  
      156 | expect(ApplyUpdates.applyUpdates).toHaveBeenCalledTimes(2);  
      157 | // validateAndApplyDeferredUpdates should be called twice, once for the initial deferred updates and once for the remaining deferred updates with gaps.  
      158 | // Unfortunately, we cannot easily count the calls of this function with Jest, since it recursively calls itself.  

      at toHaveBeenCalledTimes (tests/unit/OnyxUpdateManagerTest.ts:155:47)  

  ● OnyxUpdateManager › should not re-fetch missing updates if the lastUpdateIDFromClient has been updated  

    expect(jest.fn()).toHaveBeenCalledTimes(expected)  

    Expected number of calls: 1  
    Received number of calls: 0  

      193 |  
      194 | // Even though there are multiple gaps in the deferred updates, we only want to fetch missing updates once per batch.  
    > 195 | expect(App.getMissingOnyxUpdates).toHaveBeenCalledTimes(1);  
      196 |  
      197 | // validateAndApplyDeferredUpdates should be called once for the initial deferred updates  
      198 | // Unfortunately, we cannot easily count the calls of this function with Jest, since it recursively calls itself.  

      at toHaveBeenCalledTimes (tests/unit/OnyxUpdateManagerTest.ts:195:47)  

  ● OnyxUpdateManager › should re-fetch missing updates if the lastUpdateIDFromClient has increased, but there are still gaps after the locally applied update  

    expect(jest.fn()).toHaveBeenCalledTimes(expected)  

    Expected number of calls: 2  
    Received number of calls: 0  

      232 |  
      233 | // Even though there are multiple gaps in the deferred updates, we only want to fetch missing updates once per batch.  
    > 234 | expect(App.getMissingOnyxUpdates).toHaveBeenCalledTimes(2);  
      235 |  
      236 | // validateAndApplyDeferredUpdates should be called twice, once for the initial deferred updates and once for the remaining deferred updates with gaps.  
      237 | // Unfortunately, we cannot easily count the calls of this function with Jest, since it recursively calls itself.  

      at toHaveBeenCalledTimes (tests/unit/OnyxUpdateManagerTest.ts:234:47)  

  ● OnyxUpdateManager › should only fetch missing updates that are not outdated (older than already locally applied update)  

    expect(jest.fn()).toHaveBeenCalledTimes(expected)  

    Expected number of calls: 1  
    Received number of calls: 0  

      277 |  
      278 | // There are no gaps in the deferred updates, therefore only one call to getMissingOnyxUpdates should be triggered  
    > 279 | expect(App.getMissingOnyxUpdates).toHaveBeenCalledTimes(1);  
      280 | })  
      281 | })  
      282 |  

      at toHaveBeenCalledTimes (tests/unit/OnyxUpdateManagerTest.ts:279:47)  

Test Suites: 2 failed, 29 passed, 31 total  
Tests: 8 failed, 297 passed, 305 total  
Snapshots: 0 total  
Time: 63.428 s

@luacmartins
Copy link
Contributor

Not sure either. They all seem unrelated. Are they consistently failing?

@ikevin127
Copy link
Contributor Author

@luacmartins They seem to be (job 1, 2 and performance) - will try another sync w/ main but if that doesn't do it, not sure what the next steps should be here 🤔

@dubielzyk-expensify
Copy link
Contributor

I don't seem to always get the highlight animation when submitting from an empty state:

CleanShot.2024-09-16.at.10.08.06.mp4

I've tried to reproduce it and get the animation once in a while, but not reliably

@dubielzyk-expensify
Copy link
Contributor

As for the animation, I tested it locally and it looks really good 😄 Great job! 👏 cc @Expensify/design for viz

@ikevin127
Copy link
Contributor Author

I don't seem to always get the highlight animation when submitting from an empty state:

@dubielzyk-expensify Thanks for double-checking this, you saved us a regression with this one as I missed checking the case when the list is empty (new account) - which caused the following two issues:

  1. When the list is empty (new account), there won't be any previous transactions -> the Search get wouldn't be called with previous logic. ✅ Fixed the logic to account for this!
  2. When the list is empty (new account), there won't be any previous transactions -> the shouldAnimateInHighlight would be false because the newSearchResultKey would try to determine if new transaction key exists in the previous transactions list, but because this is empty on a new account -> newSearchResultKey would be undefined hence the newly added transaction wouldn't animate in / highlight. ✅ Fixed the logic to account for this!

This is how it looks now on a new account when creating the first transaction:

Screen.Recording.2024-09-16.at.13.23.02.mov

@dubielzyk-expensify
Copy link
Contributor

Boom! That's looking excellent

@luacmartins
Copy link
Contributor

Nice! This is looking great. @ikevin127 we have some conflicts. Also is this ready for review?

@ikevin127

This comment was marked as outdated.

@ikevin127
Copy link
Contributor Author

ikevin127 commented Sep 19, 2024

♻️ I will look into this today, use some AI to try and find a link between my changes and the tests that are failing.

Update: Turns out the reason why tests are failing is caused by the usage of useAnimatedHighlightStyle in BaseListItem. I'm looking into fixing the tests and getting this ready for review.

Update 2: Because the useAnimatedHighlightStyle hook uses useScreenWrapperTransitionStatus I had to add targeted mocks of this hook to the files where the tests were failing and also to the SelectionList.perf-test.tsx in order to have all tests (including performance) pass.

Note

I had to target specific files because if I were to add the useScreenWrapperTransitionStatus mock gloabally in jest/setup.ts then other unrelated tests would fail because not all components are using that hook.

@ikevin127
Copy link
Contributor Author

@luacmartins Thanks for double checking this and catching the bugs 👍 I just applied the fixes for the two bugs mentioned (ref1, ref2) and also performed the refactoring requested in #49192 (comment) which ensures we keep the logic in the Search component small.

Here's a video after the fix was applied:

bugfixes.mov

Please check this again and let me know if there are any other issues 🤞

@luacmartins
Copy link
Contributor

Nice! Thanks for working on those. It seems like a few tests are failing. @ishpaul777 could you please review this PR again once the tests above are fixed?

@ishpaul777
Copy link
Contributor

i will rereview and try to finish this in couple of hours 🙏

@ishpaul777
Copy link
Contributor

ishpaul777 commented Sep 26, 2024

Bug: Changing sorting, highlights and scroll to random expense item

Screen.Recording.2024-09-26.at.8.39.19.PM.mov

@ishpaul777
Copy link
Contributor

Bug: Search results scroll and highlights expense when changing status from outstading to All

Screen.Recording.2024-09-26.at.8.55.46.PM.mov

@ishpaul777
Copy link
Contributor

Bug: while scrolling down search result, there is auto scroll randomly and sometimes random expense is hightlighted (This might requires account with many (~100+) expenses)

Screen.Recording.2024-09-26.at.8.59.52.PM.mov

@ikevin127

This comment was marked as outdated.

@ikevin127
Copy link
Contributor Author

ikevin127 commented Sep 27, 2024

@ishpaul777 ♻️ I refactored the code to use a single hook (useSearchHighlightAndScroll) and improved the logic to fix the 3 issues you mentioned above. Please test again and let me know if there are any other issues!

Note: The ❌ Prettier check fail is not related to the changes here because I ran it before pushing the commit and everything was fine.

@ishpaul777
Copy link
Contributor

About to leave for the day, i will take a peek now, and review afresh tomorrow 🙏

@ishpaul777
Copy link
Contributor

we have conflicts here @ikevin127


i did not face those bugs (or any other bugs) again in my testing, and also code wise it Looks good!

Copy link
Contributor

@luacmartins luacmartins left a comment

Choose a reason for hiding this comment

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

LGTM

@luacmartins luacmartins merged commit 8f5f609 into Expensify:main Sep 30, 2024
17 checks passed
@OSBotify
Copy link
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.

Copy link
Contributor

github-actions bot commented Oct 2, 2024

🚀 Deployed to staging by https://github.com/luacmartins in version: 9.0.43-0 🚀

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

1 similar comment
Copy link
Contributor

github-actions bot commented Oct 2, 2024

🚀 Deployed to staging by https://github.com/luacmartins in version: 9.0.43-0 🚀

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

@Julesssss
Copy link
Contributor

Hey we have a minor regression here. I'm removing the blocker label, and let me know if you'd like to handle this as a follow up.

@ikevin127

This comment was marked as outdated.

@ikevin127
Copy link
Contributor Author

ikevin127 commented Oct 2, 2024

@Julesssss ✅ I confirm that #50046 is indeed a regression of this PR and I will open a follow-up PR to fix it since this is still in the regression period.

Actually, the PR's reviewer will open a follow-up and I will review it, coming from #50046 (comment).

Copy link
Contributor

github-actions bot commented Oct 3, 2024

🚀 Deployed to production by https://github.com/jasperhuangg in version: 9.0.43-6 🚀

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

@@ -77,6 +77,9 @@ function ReportListItem<TItem extends ListItem>({
styles.overflowHidden,
item.isSelected && styles.activeComponentBG,
isFocused && styles.sidebarLinkActive,
// Removing some of the styles because they are added to the parent OpacityView via animatedHighlightStyle
{backgroundColor: 'unset'},
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This style overriding caused the following issue:

Which we fixed by moving the style up, under the overflowHidden and changing it to styles.bgTransparent for compatibility with native.

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.

6 participants