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

Use fallback user avatar in cases where the user is unknown to us #41846

Merged

Conversation

Kicu
Copy link
Contributor

@Kicu Kicu commented May 8, 2024

Details

  • this is PR is a followup after this: Use fallback user avatar in cases where the user is unknown to us #39229 was reverted, it also fixes issues that were found after the original PR merge
  • the logic that decides which avatar to show is now consolidated into the <Avatar> component, almost all calls to getAvatar and getDefaultAvatar were removed as they were mostly concerned with showing a correct SVG avatar version - now <Avatar> does all this
  • right now the source of truth is almost always avatar prop from personalDetails. It is returned from backend, and any user "known" to us will have it set
  • we will now display FallbackAvatar when:
    • there is no details/accountID
    • when accountID was optimistically set in TS code
    • as a final fallback in Avatar component (that behaviour is unchanged)
  • special note for icons: whenever we create icons array/object we have to explicitly set FallbackAvatar as source. Otherwise nothing would be shown because of patterns like this one: https://github.com/Expensify/App/blob/main/src/components/OptionRow.tsx#L209

Extra notes for reviewer:

CC @s77rt

Fixed Issues

$ #38743
$ #40989
$ #40996

PROPOSAL:

Tests

  1. go to every place in the app where we can display an unknown user and verify that the avatar next to them is the fallback one (and not the colorful one)
  2. places to verify:
  • search option from Chats list
  • searching users from "Start chat"
  • searching users from "Split expense"
  • searching users from "Pay someone"
  • searching users from "Assign Task"
  • searching users from Workspace list > Workspace > Members > Invite member
  1. test [Payment Request] [$250] Mention - Avatar is not displaying on Concierge in mention list #41014:
    verify that small avatars in mentions from chat are correct including Concierge avatar
  2. test [HOLD for payment 2024-06-06] [No payment] Workspace - Not here page shows up when opening profile of invited user in offline mode #40989:
  • go to Workspace list > Workspace > Members
  • turn on offline mode (disable network in devtools) and try adding a non-existing user to members
  • verify that profile page opens
  1. test [HOLD for payment 2024-06-06] [HOLD for payment 2024-05-06] [HOLD for payment 2024-05-03] Quick action - Fallback avatar is displayed on quick action after user avatar loads #40996:
  • check if the avatar in quick action from Popover menu is the same as newly added user

Offline tests

QA Steps

  • 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
rec-android-fallback.mp4
Android: mWeb Chrome
iOS: Native
recsm-ios-fallback-demo.mp4
iOS: mWeb Safari
MacOS: Chrome / Safari
rec-web-fallback-demo.mp4
rec-web-fallback-page-err.mp4
rec-web-fallback-quickaction.mp4
MacOS: Desktop

@Kicu Kicu marked this pull request as ready for review May 8, 2024 14:11
@Kicu Kicu requested a review from a team as a code owner May 8, 2024 14:11
@melvin-bot melvin-bot bot requested review from s77rt and removed request for a team May 8, 2024 14:11
Copy link

melvin-bot bot commented May 8, 2024

@s77rt 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]

// We pass the color styles down to the SVG for the workspace and fallback avatar.
const source = isWorkspace ? originalSource : UserUtils.getAvatar(originalSource, Number(accountID));
// if it's user avatar then accountID will be a number
const source = isWorkspace ? originalSource : UserUtils.getAvatar(originalSource, accountID as number);
Copy link
Contributor Author

@Kicu Kicu May 8, 2024

Choose a reason for hiding this comment

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

@s77rt any better ideas for "accountID as number"?

the logic here is

  • if isWorkspace is true -> the accountID will be a policyID which is a string
  • if isWorkspace false -> accountID is standard user account id which is a number

I dislike casting via as, but not sure how to improve this line

Copy link
Contributor

Choose a reason for hiding this comment

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

We can split the types into two and use union

type AvatarProps = {
    ...
} & (
    | {
          type: typeof CONST.ICON_TYPE_AVATAR;
          accountID?: number;
      }
    | {
          type: typeof CONST.ICON_TYPE_WORKSPACE;
          accountID?: string;
      }
);

However for this to work we need to make the type prop required and remove its default value.

Copy link
Contributor

@s77rt s77rt May 8, 2024

Choose a reason for hiding this comment

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

The changes here seem unrelated. Can you please double check

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're asking about ProfilePage? So the changes are deliberate, and related to fixing #40989.

Basically right now (on main) the decision to show most of the content is based on this line:

const hasMinimumDetails = !isEmptyObject(details.avatar);

(https://github.com/Expensify/App/blob/main/src/pages/ProfilePage.tsx#L154)

But we know that there will be no avatar in case we're offline and rely on optimistic data - as I removed the default avatar from optimistic data almost everywhere.
All the other basic data is there, so I just allow everything to display and the Fallback avatar will be correctly shown.
I also disable clicking on the avatar when it's the fallback displayed

All the other changes are just indentation moved 1 level up. I tested this both in offline and online and it behaves correct I believe.

src/libs/UserUtils.ts Show resolved Hide resolved
src/pages/settings/InitialSettingsPage.tsx Outdated Show resolved Hide resolved
src/types/onyx/OnyxCommon.ts Show resolved Hide resolved
source?: UserUtils.AvatarSource;

/** account id if it's user avatar */
accountID?: number;
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
accountID?: number;
accountID: number | undefined;

This should help catch cases where we are missing to pass accountID.

(do same with Avatar component)

Copy link
Contributor Author

@Kicu Kicu May 10, 2024

Choose a reason for hiding this comment

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

Will do it for every Avatar.

What do you think about doing the same for source?
Now it looks kinda suspicious that I will have:source?: string but accountID as explicit: number | undefined almost as if accountID is more important than source.


I used this pattern to find all cases for TS fails, but after this I reverted back to optional with ?. In 2 cases I had to explicitly pass undefined and it looked weird IMO. We don't do this pattern anywhere else in code.
There are 2 cases where accountID will be undefined on purpose:

@Kicu
Copy link
Contributor Author

Kicu commented May 10, 2024

@s77rt thanks for all the comments, some notes from me:

  • please take a look at: https://github.com/Expensify/App/pull/41485/files as it introduces some changes related to workspace and it will most likely get merged before our PR
  • I decided that always passing explicitly accountID={undefined} looks out of place as its almost never done in this project, so I kept accountID? as optional
  • also: <MenuItem> renders <Avatar>, and MenuItem is rendered 100+ times in the App - I don't want to add accountID into all this places
  • about Icon read my comment, I would really like to return to source prop being not optional there. I think it will be better for the codebase in the long run

Comment on lines 1620 to 1625
userToInvite.icons = [
{
source: UserUtils.getAvatar('', optimisticAccountID),
source: FallbackAvatar,
name: searchValue,
type: CONST.ICON_TYPE_AVATAR,
},
Copy link
Contributor

Choose a reason for hiding this comment

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

Add id: optimisticAccountID

@@ -418,7 +417,7 @@ function getOptionData({
result.subtitle = subtitle;
result.participantsList = participantPersonalDetailList;

result.icons = ReportUtils.getIcons(report, personalDetails, UserUtils.getAvatar(personalDetail?.avatar ?? '', personalDetail?.accountID), '', -1, policy);
result.icons = ReportUtils.getIcons(report, personalDetails, personalDetail?.avatar, '', -1, policy);
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
result.icons = ReportUtils.getIcons(report, personalDetails, personalDetail?.avatar, '', -1, policy);
result.icons = ReportUtils.getIcons(report, personalDetails, personalDetail?.avatar, personalDetail?.login, personalDetail?.accountID, policy);

The avatar relays on the account id, if we pass -1 we will end up using the fallback avatar instead of the real avatar (if the used avatar is the default colorful one)

@@ -117,7 +120,7 @@ function ReportActionItemSingle({
}

secondaryAvatar = {
source: UserUtils.getAvatar(secondaryUserAvatar, secondaryAccountId),
source: secondaryUserAvatar,
Copy link
Contributor

@s77rt s77rt May 11, 2024

Choose a reason for hiding this comment

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

NAB. secondaryUserAvatar is using an empty string as the fallback, let's use the fallback avatar instead

// We pass the color styles down to the SVG for the workspace and fallback avatar.
const source = isWorkspace ? originalSource : UserUtils.getAvatar(originalSource, Number(accountID));
// if it's user avatar then accountID will be a number
const source = isWorkspace ? originalSource : UserUtils.getAvatar(originalSource, accountID as number);
Copy link
Contributor

Choose a reason for hiding this comment

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

We can split the types into two and use union

type AvatarProps = {
    ...
} & (
    | {
          type: typeof CONST.ICON_TYPE_AVATAR;
          accountID?: number;
      }
    | {
          type: typeof CONST.ICON_TYPE_WORKSPACE;
          accountID?: string;
      }
);

However for this to work we need to make the type prop required and remove its default value.

@s77rt
Copy link
Contributor

s77rt commented May 11, 2024

Bug: In the task assignee the assign to me row has the fallback avatar

Screenshot 2024-05-11 at 2 16 16 PM

@Kicu
Copy link
Contributor Author

Kicu commented May 13, 2024

All changes pushes @s77rt
I like the types suggestion but in the end I don't want to add any more changes because this PR is constantly growing, maybe in future we can add the union.

For the bug that you found there is something wrong in the whole chain of calls : TaskAssigneeSelectorModalProps -> useOptionsList -> OptionsListUtils.createOptionList -> createOption which returns a list of options that include empty Icon only for current user.
Its simpler to recreate the proper icon. Investigating this chain of calls was extremely difficult and in the end I don't know where to fix it.

Screenshot 2024-05-13 at 16 51 06

@Kicu Kicu requested a review from s77rt May 13, 2024 14:55
@s77rt
Copy link
Contributor

s77rt commented May 13, 2024

Can you please test if that bug is still reproducible? (without the workaround) I'm getting a different icon now which is also wrong but same as staging

Screenshot 2024-05-13 at 11 14 06 PM

@Kicu
Copy link
Contributor Author

Kicu commented May 14, 2024

So without workaround it reproduces for me every time on web. I have a feeling that whether you get this bug is related to state of personalDetails key in onyx, because the logic that generates these options combines together a lot of data.
If I explicitly search for myself then sometimes the avatar updates, but after a full page refresh I'm always back to the bug.

However I tried running it on ios and there I had the correct avatar :/
I think its better to keep the workaround if there are some cases where this bug appears

@s77rt
Copy link
Contributor

s77rt commented May 14, 2024

In main / staging can you reproduce the bug? If not then it's a regression and we should fix it without workarounds

@Kicu
Copy link
Contributor Author

Kicu commented May 14, 2024

I'm sorry I misunderstood. We are talking about Task > "Assign to me" bug correct?

  • In staging I cannot reproduce, it works correctly
  • However on local dev on newest main I still get the bug (without my code)
  • local dev on newest main but a brand new account - no bug

I strongly believe the bug is related to current state of one's local DB and what personalDetails are saved there. So we can treat it as not a regression (at least on my machine it behaves like not a regression in some cases).

That being said I would still keep the workaround as it uses useCurrentUserPersonalDetails() hook which sounds like a good source of current user data.

@s77rt
Copy link
Contributor

s77rt commented May 14, 2024

Can you please resolve the conflicts and remove the workaround? I cannot reproduce this constantly but feels like we should handle it separately

@grgia
Copy link
Contributor

grgia commented May 17, 2024

@shawnborton or @dubielzyk-expensify I have one more avatar related PR- I just spun up a test build if you'd like to take a look. This one handles the changes to fallback avatars after the first take was reverted

Copy link
Contributor

@shawnborton
Copy link
Contributor

Found a missing spot when viewing the workspace avatar from the workspace overview page:

CleanShot.2024-05-17.at.08.42.40.mp4

@Kicu
Copy link
Contributor Author

Kicu commented May 17, 2024

@shawnborton great find.

I checked that the same bug is currently appearing on web so its not introduced by my code, but rather another bug after this: #39637

However if thats okey with you guys I could try to fix this within this PR as hopefully that will be faster.

@Kicu
Copy link
Contributor Author

Kicu commented May 17, 2024

@shawnborton @grgia
I fixed 😄 - turns out it was an extremely simple fix

rec-bug.mov

@shawnborton
Copy link
Contributor

Amazing, thanks!

@@ -138,7 +138,7 @@ function ReportActionItemSingle({
source: avatarSource ?? FallbackAvatar,
type: isWorkspaceActor ? CONST.ICON_TYPE_WORKSPACE : CONST.ICON_TYPE_AVATAR,
name: primaryDisplayName ?? '',
id: avatarAccountId,
id: isWorkspaceActor ? report.policyID : avatarAccountId,
Copy link
Contributor

Choose a reason for hiding this comment

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

NAB. avatarAccountId already takes into account the isWorkspaceActor condition. Change the variable name to avatarId to avoid confusion

Copy link
Contributor

@mountiny mountiny left a comment

Choose a reason for hiding this comment

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

@Kicu Couple minor comments, can you please sync up with main too? Thanks!

src/components/AvatarWithIndicator.tsx Outdated Show resolved Hide resolved
src/components/Avatar.tsx Outdated Show resolved Hide resolved
src/libs/ReportUtils.ts Outdated Show resolved Hide resolved
src/libs/UserUtils.ts Outdated Show resolved Hide resolved
@Kicu
Copy link
Contributor Author

Kicu commented May 23, 2024

@mountiny Done and done

Copy link
Contributor

@mountiny mountiny left a comment

Choose a reason for hiding this comment

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

Thanks @Kicu Looks good to me

@grgia can you please review?

@mountiny mountiny merged commit 790ab50 into Expensify:main May 28, 2024
14 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.

@OSBotify
Copy link
Contributor

🚀 Deployed to staging by https://github.com/mountiny in version: 1.4.77-0 🚀

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

@OSBotify
Copy link
Contributor

🚀 Deployed to production by https://github.com/puneetlath in version: 1.4.77-11 🚀

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

@OSBotify
Copy link
Contributor

🚀 Deployed to production by https://github.com/puneetlath in version: 1.4.77-11 🚀

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

@Kicu Kicu deleted the kicu/38743-fallback-avatar-fix branch July 24, 2024 14:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants