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

Rebuild unread reports without sequence numbers #13419

Merged
merged 55 commits into from
Jan 3, 2023
Merged

Conversation

puneetlath
Copy link
Contributor

@puneetlath puneetlath commented Dec 7, 2022

On HOLD until https://github.com/Expensify/Web-Expensify/pull/35907 is live.

Details

This PR rebuilds the unread functionality to use reporAction IDs and timestamps instead of sequence numbers. At a high-level:

  1. Every report action has a unique reportActionID (non-sequential)
  2. Every report action has a created field with millisecond precision
  3. Every report has a lastReadTime indicating the last time that report was read
  4. Every report was a lastActionCreated indicating the reportAction.created of the last visible report action on the report
  5. A report is unread if lastReadTimes < lastActionCreated
  6. To mark a given action as unread, we set the lastReadTime to 1 millisecond before the reportAction.created of that action
  7. We display the "new" message indicator above the oldest action whose reportAction.created is after the lastReadTimestamp

Fixed Issues

$ https://github.com/Expensify/Expensify/issues/240916
$ #13472
$ #13684

Tests

  1. Create two accounts, A and B, that have never chatted before.
  2. Send a DM from B to A. The chat should be:
    • Unread for A
    • Read for B
  3. User A opens the chat. The chat should become Read
  4. User A sends a message in the chat. It should remain Read
  5. When user A has the chat fully visible and scrolled to the bottom, user B sends a message to user A. The chat should remain Read for both users.
  6. User A sends a bunch of messages, such that they can scroll back in the chat and the bottom messages aren't visible. User A then scrolls back up in the chat.
  7. User B sends a message to user A. The chat should become unread for user A.
  8. User B sends two more messages to user. The chat should remain unread.
  9. User A scrolls back down to the bottom of the chat. It should stay unread for now, and a new message indicator should appear above the first message that user B sent while user A was scrolled up.
  10. (narrow screens) User A goes to the LHN, opens a different chat, then goes back to the LHN. The chat with user B should now show as read.
  11. (narrow screens) User A returns to the DM with user B and starts uploading a large attachment.
  12. (narrow screens) Before the upload finishes, User A goes back to the LHN.
  13. (narrow screens) Wait for a bit until the upload completes. Verify that the chat remains read and does not become unread.
  14. User A navigates to a different chat.
  15. User B sends a message to User A. The chat should become unread for User A.
  16. User A opens the chat from user B, then navigates to a different chat, then comes back. It should now be read.
  17. User A scrolls up, marks a given message as Unread. The chat should become unread and an unread indicator should appear above the message that was marked as unread.
  18. User A leaves the chat, opens a different one, then comes back. The unread marker should be where it was in the previous step, and the chat should become read.
  19. User A opens a different chat, then comes back. The unread marker should be gone.
  20. User A scrolls back, then marks one of their own messages in the middle of the chat as unread. The chat should become unread and a new message indicator should appear above that message.
  21. User A deletes the message they marked as unread.
  22. User A opens a different chat, then comes back. The new message indicator should be above the message after the deleted message, and the chat should become read.
  23. User A opens a different chat, then comes back. The new message indicator should be gone.
  24. User A sends a message in the chat, then marks it (i.e: the newest message in the chat) as unread. The chat should become unread and the new message indicator should appear above that message.
  25. User A then deletes the message they marked as unread. The chat should become read and the new message indicator should disappear.
  26. User A sends a message in the chat, then marks it (i.e: the newest message in the chat) as unread. The chat should become unread (bolded) and the new message indicator should appear above that message.
  27. User A navigates to a different chat, closes the app completely (by closing the tab (web), pressing the home button (mobile), or exiting electron (desktop)) and reopens it. The chat/message from the previous step should still be unread.
  28. User A opens the chat, opens a different chat, then comes back, and the message should be read.
  29. User A closes the app completely and reopens it. The chat should remain read.
  • Verify that no errors appear in the JS console

Offline tests

  1. User A goes offline.
  2. User B sends a DM to user A. User A is offline so their chat should remain unchanged.
  3. User A sends an (offline) DM to User B. User A should see the chat as read.
  4. User A comes back online. Since their message was sent most recently, the chat should appear as read.
    • Potential improvement: As described here, we could change the unread feature such that the chat appears unread for User A in this scenario, but that's out-of-scope for now.

QA Steps

Run through any existing regression tests that deal with unread chat functionality.

  • 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 / Chrome
    • iOS / native
    • iOS / 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 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 correct English and 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 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(themeColors.componentBG)
  • 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 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.
  • I have checked off every checkbox in the PR author checklist, including those that don't apply to this PR.

Screenshots/Videos

Web
Web.mov
Mobile Web - Chrome
android.chrome.mov
Mobile Web - Safari
ios.safari.mov
Desktop
desktop.mov
iOS
ios.mov
Android
android.mov

@puneetlath puneetlath self-assigned this Dec 7, 2022
@puneetlath puneetlath changed the title Rebuild unread reports without sequence numbers [WIP] Rebuild unread reports without sequence numbers Dec 14, 2022
@puneetlath puneetlath marked this pull request as ready for review December 14, 2022 22:42
@puneetlath puneetlath requested a review from a team as a code owner December 14, 2022 22:42
@melvin-bot melvin-bot bot requested review from deetergp and rushatgabhane and removed request for a team December 14, 2022 22:43
@melvin-bot
Copy link

melvin-bot bot commented Dec 14, 2022

@rushatgabhane @deetergp 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]

@puneetlath
Copy link
Contributor Author

@rushatgabhane FYI you won't be able to test this yet as it requires some back-end changes that you don't yet have access to.

@puneetlath puneetlath requested review from roryabraham and removed request for rushatgabhane December 15, 2022 14:46
@marcaaron
Copy link
Contributor

I think we should re-write these test steps so that there is less ambiguity about what we're looking for. I generally refer to any indication that a report is unread as an "unread indicator" but I think maybe you are using it to refer to the "new marker line" e.g. in step 22

User A leaves the report, opens a different one, then comes back. The unread marker should be where it was in the previous step, and the report should become read.

If I understand this step correctly it is not the expected behavior. If you navigate away from a report that had the "new marker line" set then it should be cleared when you navigate away from the report. It shouldn't matter if you open a different report or not.

@marcaaron
Copy link
Contributor

Step 25 - Again, we should move failing tests out of the tests or test the expected behavior (even if we expect it to fail). I'd probably say we should avoid testing behaviors we know will fail because sometimes QA uses these tests in the future to assume a correct behavior.

@marcaaron
Copy link
Contributor

I think step 26 is also hard to test because it's dependent on 25.

User A opens a different report, then comes back. The new message indicator should be where is was before, and the chat should become read.

What state should we be in for this to work? I think it's right after we mark a message as unread? But in the tests it is after we delete a report action.

@marcaaron
Copy link
Contributor

Step 31 is a little ambiguous too

User A closes the app completely and reopens it. The message from the previous step should still be unread.

Close the app how? Can we be more specific and if there are platform specific things we are doing?

@marcaaron marcaaron self-requested a review January 2, 2023 22:34
Copy link
Contributor

@marcaaron marcaaron left a comment

Choose a reason for hiding this comment

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

Code LGTM! Ran into some trouble following the test steps. I get the sense that whatever is broken about the unread indicators at this point is consistent with staging or some possible recent regressions. I haven't looked into the source of those regressions, but certainly doesn't seem like this PR is going to make things any worse.

@puneetlath
Copy link
Contributor Author

Thanks @marcaaron! The tests have been updated. The steps that are in the PR now should bring us up to parity with main and should all be passing.

@marcaaron
Copy link
Contributor

marcaaron commented Jan 2, 2023

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 / Chrome
    • iOS / native
    • iOS / 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 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 correct English and 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(themeColors.componentBG)
  • 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 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.
  • I have checked off every checkbox in the PR reviewer checklist, including those that don't apply to this PR.

Screenshots/Videos

Web

2023-01-02_14-12-21

Mobile Web - Chrome

2023-01-02_15-05-56

Mobile Web - Safari

2023-01-02_14-50-17

Desktop

2023-01-02_14-33-43

iOS

2023-01-02_14-22-53

Android

2023-01-02_15-01-08

@marcaaron
Copy link
Contributor

User A opens the report, navigates to a different one, then comes back. It should now be read.

should say

User A opens the report, navigates to LHN?, selects a different report, navigates back to the LHN. Verify the chat in the LHN is read

?

@marcaaron
Copy link
Contributor

Found this while testing on iOS (maybe it is a known issue)

  1. Mark chat message as unread
  2. Navigate to LHN
  3. Close app
  4. Reopen app
  5. Unread status of report flashes from "read" to "unread" then eventually "read" 🐛
  6. Navigate to chat
  7. No "new marker" present 🐛
2023-01-02_14-24-35.mp4

@marcaaron
Copy link
Contributor

Sorry this one is pretty wonky, but on mobile web (Safari) I performed these steps:

  1. Visit a report where there was just one new comment
  2. New marker shows in the correct spot ✅
  3. Scroll up in the chat
  4. Pull down to refresh
  5. LHN will show (report is now "read") ✅
  6. Tap back into chat and scroll up
  7. Chat messages disappear briefly 🤔
  8. When they return the new marker is in the wrong spot 💥
2023-01-02_14-40-41.mp4

Didn't see this on any other platforms so far...

@marcaaron
Copy link
Contributor

marcaaron commented Jan 3, 2023

Minor issue while testing mobile Safari (Edit: also happens on Android)

  1. Mark last comment as read
  2. Close Safari
  3. Reopen Safari
  4. New marker is not shown but appears a short time later 🐛
2023-01-02_14-47-54.mp4

@marcaaron
Copy link
Contributor

I don't think it was introduced here but for transparency there is a propType warning popping up when you long press on a comment (found while testing Android)

2023-01-02_14-58-29

@marcaaron
Copy link
Contributor

Great work @puneetlath!

Completed testing on various platforms. Turned up a few issues (minor I think). I'm also not certain if those are on staging or not (sorry testing took several hours). My personal opinion on this is that the things turned up are not urgent. There are a few open issues related to the unread indicators floating around and I don't think having this PR open any longer will help us solve those issues. So seeing as nothing major is broken I am 👍

That said, it does seem like something has recently regressed related to setting the "new marker" and unread status in the LHN. This stuff was working (definitely not perfectly, but I think better than it is working now) and we should commit to exploring improvements in the ReportScreen 2.0 conversation started by @roryabraham here.

@puneetlath
Copy link
Contributor Author

puneetlath commented Jan 3, 2023

Thanks for the super thorough testing!

Found this while testing on iOS (maybe it is a known issue)

Mark chat message as unread
Navigate to LHN
Close app
Reopen app
Unread status of report flashes from "read" to "unread" then eventually "read" 🐛
Navigate to chat
No "new marker" present 🐛

This one is known and exists on main too. This is the same issue as this: #13799

Sorry this one is pretty wonky, but on mobile web (Safari) I performed these steps:

Visit a report where there was just one new comment
New marker shows in the correct spot ✅
Scroll up in the chat
Pull down to refresh
LHN will show (report is now "read") ✅
Tap back into chat and scroll up
Chat messages disappear briefly 🤔
When they return the new marker is in the wrong spot 💥

I wasn't able to reproduce this one on main. I'll open an issue for it. It feels sufficiently niche to not block over.

Minor issue while testing mobile Safari (Edit: also happens on Android)

Mark last comment as read
Close Safari
Reopen Safari
New marker is not shown but appears a short time later 🐛

This one also exists on main. I'll open an issue.

I don't think it was introduced here but for transparency there is a propType warning popping up when you long press on a comment (found while testing Android)

Confirmed this is also happening on main. I'll open an issue. (link)

@puneetlath puneetlath changed the title [HOLD web-e #35907] Rebuild unread reports without sequence numbers Rebuild unread reports without sequence numbers Jan 3, 2023
@chiragsalian
Copy link
Contributor

chiragsalian commented Jan 3, 2023

Discussed on slack, i'm proceeding to merge this.

@chiragsalian chiragsalian dismissed roryabraham’s stale review January 3, 2023 23:28

stale review and it was tackled

@chiragsalian chiragsalian merged commit 9a0f817 into main Jan 3, 2023
@chiragsalian chiragsalian deleted the unread-refactor branch January 3, 2023 23:29
@OSBotify
Copy link
Contributor

OSBotify commented Jan 3, 2023

✋ 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
Contributor

OSBotify commented Jan 4, 2023

🚀 Deployed to staging by @chiragsalian in version: 1.2.48-0 🚀

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

@OSBotify
Copy link
Contributor

OSBotify commented Jan 5, 2023

🚀 Deployed to production by @luacmartins in version: 1.2.48-2 🚀

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

@OSBotify
Copy link
Contributor

OSBotify commented Jan 6, 2023

🚀 Deployed to production by @luacmartins in version: 1.2.49-0 🚀

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

Comment on lines +1019 to +1022
// lastActionCreated and lastReadTime are both datetime strings and can be compared directly
const lastActionCreated = report.lastActionCreated || '';
const lastReadTime = report.lastReadTime || '';
return lastReadTime < lastActionCreated;
Copy link
Contributor

Choose a reason for hiding this comment

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

Edge case: If a chat report is empty (has no report actions except the CREATED action) then lastReadTime wouldn't be set and we fallback to an empty string and '' < lastActionCreated will be true making an empty chat unread (#38368).

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.

10 participants