diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Users/UsersProfile/UserProfileDetails/UserProfileDetails.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Users/UsersProfile/UserProfileDetails/UserProfileDetails.component.tsx index 68577edcfd3..c4c088ddb56 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Users/UsersProfile/UserProfileDetails/UserProfileDetails.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Users/UsersProfile/UserProfileDetails/UserProfileDetails.component.tsx @@ -14,6 +14,7 @@ import { ExclamationCircleFilled } from '@ant-design/icons'; import { Button, Divider, Input, Space, Tooltip, Typography } from 'antd'; import { AxiosError } from 'axios'; +import { isEmpty } from 'lodash'; import React, { useCallback, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { ReactComponent as EditIcon } from '../../../../../assets/svg/edit-new.svg'; @@ -94,14 +95,16 @@ const UserProfileDetails = ({ [userData] ); - const onDisplayNameChange = (e: React.ChangeEvent) => - setDisplayName(e.target.value); + const onDisplayNameChange = useCallback( + (e: React.ChangeEvent) => setDisplayName(e.target.value), + [] + ); const handleDisplayNameSave = useCallback(async () => { if (displayName !== userData.displayName) { setIsLoading(true); await updateUserDetails( - { displayName: displayName ?? '' }, + { displayName: isEmpty(displayName) ? undefined : displayName }, 'displayName' ); setIsLoading(false); @@ -114,67 +117,70 @@ const UserProfileDetails = ({ setIsDisplayNameEdit(false); }, [userData.displayName]); - const displayNameRenderComponent = useMemo( - () => - isDisplayNameEdit ? ( - - - - ) : ( - - - {hasEditPermission - ? userData.displayName || - t('label.add-entity', { entity: t('label.display-name') }) - : getEntityName(userData)} - - {hasEditPermission && ( - - { - // Used to stop click propagation event to parent User.component collapsible panel - e.stopPropagation(); - setIsDisplayNameEdit(true); - }} - /> - - )} - - ), - [ - userData, - displayName, - isDisplayNameEdit, - hasEditPermission, - getEntityName, - onDisplayNameChange, - handleDisplayNameSave, - handleCloseEditDisplayName, - ] - ); + const displayNameRenderComponent = useMemo(() => { + const displayNamePlaceHolder = isEmpty(userData.displayName) + ? t('label.add-entity', { entity: t('label.display-name') }) + : userData.displayName; + + const displayNameText = hasEditPermission + ? displayNamePlaceHolder + : getEntityName(userData); + + return isDisplayNameEdit ? ( + + + + ) : ( + + + {displayNameText} + + {hasEditPermission && ( + + { + // Used to stop click propagation event to parent User.component collapsible panel + e.stopPropagation(); + setIsDisplayNameEdit(true); + }} + /> + + )} + + ); + }, [ + userData, + displayName, + isDisplayNameEdit, + hasEditPermission, + getEntityName, + onDisplayNameChange, + handleDisplayNameSave, + handleCloseEditDisplayName, + ]); const changePasswordRenderComponent = useMemo( () => diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Users/UsersProfile/UserProfileDetails/UserProfileDetails.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Users/UsersProfile/UserProfileDetails/UserProfileDetails.test.tsx index c2156aaf76c..526c46c9c03 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Users/UsersProfile/UserProfileDetails/UserProfileDetails.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Users/UsersProfile/UserProfileDetails/UserProfileDetails.test.tsx @@ -11,6 +11,7 @@ * limitations under the License. */ import { act, fireEvent, render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import React from 'react'; import { MemoryRouter } from 'react-router-dom'; import { AuthProvider } from '../../../../../generated/settings/settings'; @@ -305,23 +306,25 @@ describe('Test User Profile Details Component', () => { }); it('should call updateUserDetails on click of DisplayNameButton', async () => { - render(, { - wrapper: MemoryRouter, + await act(async () => { + render(, { + wrapper: MemoryRouter, + }); }); - act(() => { + await act(async () => { fireEvent.click(screen.getByTestId('edit-displayName')); }); expect(screen.getByText('InlineEdit')).toBeInTheDocument(); - act(() => { + await act(async () => { fireEvent.change(screen.getByTestId('displayName'), { target: { value: 'test' }, }); }); - act(() => { + await act(async () => { fireEvent.click(screen.getByTestId('display-name-save-button')); }); @@ -331,6 +334,43 @@ describe('Test User Profile Details Component', () => { ); }); + it('should pass displayName undefined to the updateUserDetails in case of empty string', async () => { + await act(async () => { + render( + , + { + wrapper: MemoryRouter, + } + ); + }); + + await act(async () => { + userEvent.click(screen.getByTestId('edit-displayName')); + }); + + expect(screen.getByText('InlineEdit')).toBeInTheDocument(); + + await act(async () => { + fireEvent.change(screen.getByTestId('displayName'), { + target: { + value: '', + }, + }); + }); + + await act(async () => { + userEvent.click(screen.getByTestId('display-name-save-button')); + }); + + expect(mockPropsData.updateUserDetails).toHaveBeenCalledWith( + { displayName: undefined }, + 'displayName' + ); + }); + it('should call updateUserDetails on click of PersonaSaveButton', async () => { render(, { wrapper: MemoryRouter, diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Users/UsersProfile/UserProfileImage/UserProfileImage.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Users/UsersProfile/UserProfileImage/UserProfileImage.test.tsx index 1635956c01b..63bf52b5e22 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Users/UsersProfile/UserProfileImage/UserProfileImage.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Users/UsersProfile/UserProfileImage/UserProfileImage.test.tsx @@ -28,13 +28,11 @@ jest.mock('../../../../../utils/ProfilerUtils', () => ({ })); jest.mock('../../../../common/ProfilePicture/ProfilePicture', () => { - return jest.fn().mockReturnValue(

ProfilePicture

); + return jest + .fn() + .mockImplementation(({ displayName }) =>

{displayName}

); }); -jest.mock('../../../../../utils/EntityUtils', () => ({ - getEntityName: jest.fn().mockReturnValue('getEntityName'), -})); - describe('Test User User Profile Image Component', () => { it('should render user profile image component', async () => { render(); @@ -49,7 +47,20 @@ describe('Test User User Profile Image Component', () => { expect(screen.getByTestId('profile-image-container')).toBeInTheDocument(); - expect(screen.getByText('ProfilePicture')).toBeInTheDocument(); + expect(screen.queryByText(mockUserData.name)).toBeNull(); + expect(screen.getByText(mockUserData.displayName)).toBeInTheDocument(); + }); + + it('should fallback to name for the displayName prop of ProfilePicture', async () => { + await act(async () => { + render( + + ); + }); + + expect(screen.getByTestId('profile-image-container')).toBeInTheDocument(); + + expect(screen.getByText(mockUserData.name)).toBeInTheDocument(); }); it('should render user profile picture component with image', async () => { diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/UserPage/UserPage.component.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/UserPage/UserPage.component.tsx index f7f1d7d7e01..6fe79b803b5 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/UserPage/UserPage.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/UserPage/UserPage.component.tsx @@ -14,7 +14,7 @@ import { Typography } from 'antd'; import { AxiosError } from 'axios'; import { compare } from 'fast-json-patch'; -import { isEmpty, isUndefined } from 'lodash'; +import { isEmpty, isUndefined, omitBy } from 'lodash'; import Qs from 'qs'; import { default as React, @@ -88,8 +88,8 @@ const UserPage = () => { }); }; - const ErrorPlaceholder = () => { - return ( + const errorPlaceholder = useMemo( + () => (
@@ -103,8 +103,9 @@ const UserPage = () => { />
- ); - }; + ), + [username] + ); const updateUserDetails = useCallback( async (data: Partial, key: keyof User) => { @@ -128,7 +129,7 @@ const UserPage = () => { ...currentUser, ...updatedKeyData, }; - const newUserData = { ...userData, ...updatedKeyData }; + const newUserData: User = { ...userData, ...updatedKeyData }; if (key === 'defaultPersona') { if (isUndefined(response.defaultPersona)) { @@ -140,7 +141,8 @@ const UserPage = () => { if (userData.id === currentUser?.id) { updateCurrentUser(newCurrentUserData as User); } - setUserData(newUserData); + // Omit the undefined values from the User object + setUserData(omitBy(newUserData, isUndefined) as User); } else { throw t('message.unexpected-error'); } @@ -173,7 +175,7 @@ const UserPage = () => { } if (isError && isEmpty(userData)) { - return ; + return errorPlaceholder; } return (