diff --git a/openmetadata-ui/src/main/resources/ui/cypress/common/Utils/Users.ts b/openmetadata-ui/src/main/resources/ui/cypress/common/Utils/Users.ts index a8ce674160e9..55831d823087 100644 --- a/openmetadata-ui/src/main/resources/ui/cypress/common/Utils/Users.ts +++ b/openmetadata-ui/src/main/resources/ui/cypress/common/Utils/Users.ts @@ -245,9 +245,9 @@ export const editTeams = (teamName: string) => { cy.get('[data-testid="inline-save-btn"]').click({ timeout: 10000 }); verifyResponseStatusCode('@updateTeams', 200); - cy.get('.ant-collapse-expand-icon > .anticon > svg').click(); - cy.get('.page-layout-v1-vertical-scroll').scrollTo(0, 0); - cy.get(`[data-testid="${teamName}"]`).should('be.visible'); + cy.get(`[data-testid="${teamName}-link"]`) + .scrollIntoView() + .should('be.visible'); }; export const handleUserUpdateDetails = ( @@ -284,8 +284,7 @@ export const handleAdminUpdateDetails = ( editDisplayName(editedUserName); // edit teams - cy.get('.ant-collapse-expand-icon > .anticon > svg').scrollIntoView(); - cy.get('.ant-collapse-expand-icon > .anticon > svg').click(); + cy.get('.ant-collapse-expand-icon > .anticon > svg').scrollIntoView().click(); editTeams(teamName); // edit description diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Users/Users.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Users/Users.component.tsx index 14dc3ba29b7a..5a943545a22b 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Users/Users.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Users/Users.component.tsx @@ -349,7 +349,8 @@ const Users = ({ userData, queryFilters, updateUserDetails }: Props) => { } + entityType={EntityType.PERSONA} + icon={} noDataPlaceholder={t('message.no-persona-assigned')} /> 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 1027a56fe06d..92bfe1a46782 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 @@ -115,12 +115,17 @@ const UserProfileDetails = ({ setIsDisplayNameEdit(false); }, [userData.displayName, displayName, updateUserDetails]); + const handleCloseEditDisplayName = useCallback(() => { + setDisplayName(userData.displayName); + setIsDisplayNameEdit(false); + }, [userData.displayName]); + const displayNameRenderComponent = useMemo( () => - isDisplayNameEdit && hasEditPermission ? ( + isDisplayNameEdit ? ( setIsDisplayNameEdit(false)} + onCancel={handleCloseEditDisplayName} onSave={handleDisplayNameSave}> 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 1036c315882d..dca3ee95a2a8 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 @@ -63,13 +63,16 @@ jest.mock('../UserProfileImage/UserProfileImage.component', () => { }); jest.mock('../../../../common/InlineEdit/InlineEdit.component', () => { - return jest.fn().mockImplementation(({ onSave, children }) => ( + return jest.fn().mockImplementation(({ onSave, onCancel, children }) => (
InlineEdit {children} +
)); }); @@ -224,6 +227,28 @@ describe('Test User Profile Details Component', () => { expect(screen.getByText('InlineEdit')).toBeInTheDocument(); }); + it('should not render changed displayName in input if not saved', async () => { + render(, { + wrapper: MemoryRouter, + }); + + fireEvent.click(screen.getByTestId('edit-displayName')); + + act(() => { + fireEvent.change(screen.getByTestId('displayName'), { + target: { value: 'data-test' }, + }); + }); + + act(() => { + fireEvent.click(screen.getByTestId('display-name-cancel-button')); + }); + + fireEvent.click(screen.getByTestId('edit-displayName')); + + expect(screen.getByTestId('displayName')).toHaveValue(''); + }); + it('should call updateUserDetails on click of DisplayNameButton', async () => { render(, { wrapper: MemoryRouter, diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Users/UsersProfile/UserProfileImage/UserProfileImage.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Users/UsersProfile/UserProfileImage/UserProfileImage.component.tsx index 53f4e58b66fe..47fef910cfb6 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Users/UsersProfile/UserProfileImage/UserProfileImage.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Users/UsersProfile/UserProfileImage/UserProfileImage.component.tsx @@ -13,6 +13,7 @@ import { Image } from 'antd'; import React, { useEffect, useMemo, useState } from 'react'; +import { getEntityName } from '../../../../../utils/EntityUtils'; import { getImageWithResolutionAndFallback, ImageQuality, @@ -50,7 +51,7 @@ const UserProfileImage = ({ userData }: UserProfileImageProps) => { /> ) : ( { return jest.fn().mockReturnValue(

ProfilePicture

); }); +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(); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Users/UsersProfile/UserProfileInheritedRoles/UserProfileInheritedRoles.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Users/UsersProfile/UserProfileInheritedRoles/UserProfileInheritedRoles.component.tsx index dbe62b8539de..fab09848755e 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Users/UsersProfile/UserProfileInheritedRoles/UserProfileInheritedRoles.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Users/UsersProfile/UserProfileInheritedRoles/UserProfileInheritedRoles.component.tsx @@ -15,6 +15,7 @@ import { Card, Typography } from 'antd'; import React from 'react'; import { useTranslation } from 'react-i18next'; import { ReactComponent as UserIcons } from '../../../../../assets/svg/user.svg'; +import { EntityType } from '../../../../../enums/entity.enum'; import Chip from '../../../../common/Chip/Chip.component'; import { UserProfileInheritedRolesProps } from './UserProfileInheritedRoles.interface'; @@ -37,6 +38,7 @@ const UserProfileInheritedRoles = ({ }> } noDataPlaceholder={t('message.no-inherited-roles-found')} /> diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Users/UsersProfile/UserProfileRoles/UserProfileRoles.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Users/UsersProfile/UserProfileRoles/UserProfileRoles.component.tsx index e6a69c06fdca..d117baffd3e7 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/Settings/Users/UsersProfile/UserProfileRoles/UserProfileRoles.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/Settings/Users/UsersProfile/UserProfileRoles/UserProfileRoles.component.tsx @@ -14,7 +14,7 @@ import { Card, Select, Space, Tooltip, Typography } from 'antd'; import { AxiosError } from 'axios'; import { isEmpty, toLower } from 'lodash'; -import React, { useEffect, useMemo, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { ReactComponent as EditIcon } from '../../../../../assets/svg/edit-new.svg'; import { ReactComponent as UserIcons } from '../../../../../assets/svg/user.svg'; @@ -24,6 +24,7 @@ import { PAGE_SIZE_LARGE, TERM_ADMIN, } from '../../../../../constants/constants'; +import { EntityType } from '../../../../../enums/entity.enum'; import { Role } from '../../../../../generated/entity/teams/role'; import { useAuth } from '../../../../../hooks/authHooks'; import { getRoles } from '../../../../../rest/rolesAPIV1'; @@ -87,6 +88,15 @@ const UserProfileRoles = ({ } }; + const setUserRoles = useCallback(() => { + const defaultUserRoles = [ + ...(userRoles?.map((role) => role.id) ?? []), + ...(isUserAdmin ? [toLower(TERM_ADMIN)] : []), + ]; + + setSelectedRoles(defaultUserRoles); + }, [userRoles, isUserAdmin]); + const handleRolesSave = async () => { setIsLoading(true); // filter out the roles , and exclude the admin one @@ -122,6 +132,7 @@ const UserProfileRoles = ({ : []), ...(userRoles ?? []), ]} + entityType={EntityType.ROLE} icon={} noDataPlaceholder={t('message.no-roles-assigned')} showNoDataPlaceholder={!isUserAdmin} @@ -130,14 +141,14 @@ const UserProfileRoles = ({ [userRoles, isUserAdmin] ); - useEffect(() => { - const defaultUserRoles = [ - ...(userRoles?.map((role) => role.id) ?? []), - ...(isUserAdmin ? [toLower(TERM_ADMIN)] : []), - ]; + const handleCloseEditRole = useCallback(() => { + setIsRolesEdit(false); + setUserRoles(); + }, [setUserRoles]); - setSelectedRoles(defaultUserRoles); - }, [isUserAdmin, userRoles]); + useEffect(() => { + setUserRoles(); + }, [setUserRoles]); useEffect(() => { if (isRolesEdit && isEmpty(roles)) { @@ -176,7 +187,7 @@ const UserProfileRoles = ({ setIsRolesEdit(false)} + onCancel={handleCloseEditRole} onSave={handleRolesSave}> + onSelectionChange([ + { + id: '37a00e0b-383c-4451-b63f-0bad4c745abc', + name: 'admin', + type: 'team', + }, + ]) + } + /> + + )); }); describe('Test User Profile Teams Component', () => { @@ -67,18 +95,26 @@ describe('Test User Profile Teams Component', () => { expect(await screen.findAllByText('Chip')).toHaveLength(1); }); - it('should render teams select input on edit click', async () => { + it('should maintain initial state if edit is close without save', async () => { render(); - expect(screen.getByTestId('user-team-card-container')).toBeInTheDocument(); + fireEvent.click(screen.getByTestId('edit-teams-button')); - const editButton = screen.getByTestId('edit-teams-button'); + const selectInput = screen.getByTestId('select-user-teams'); + + act(() => { + fireEvent.change(selectInput, { + target: { + value: 'test', + }, + }); + }); - expect(editButton).toBeInTheDocument(); + fireEvent.click(screen.getByTestId('cancel')); - fireEvent.click(editButton); + fireEvent.click(screen.getByTestId('edit-teams-button')); - expect(screen.getByText('InlineEdit')).toBeInTheDocument(); + expect(screen.getByText('Organization')).toBeInTheDocument(); }); it('should call updateUserDetails on click save', async () => { @@ -95,7 +131,14 @@ describe('Test User Profile Teams Component', () => { }); expect(mockPropsData.updateUserDetails).toHaveBeenCalledWith( - { teams: [] }, + { + teams: [ + { + id: '9e8b7464-3f3e-4071-af05-19be142d75db', + type: 'team', + }, + ], + }, 'teams' ); }); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/Chip/Chip.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/Chip/Chip.component.tsx index 646d5f314b75..318a9f588085 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/Chip/Chip.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/Chip/Chip.component.tsx @@ -13,17 +13,21 @@ import { Col, Popover, Row, Space, Tag, Typography } from 'antd'; import { isEmpty } from 'lodash'; import React, { useEffect, useMemo, useState } from 'react'; +import { Link } from 'react-router-dom'; import { NO_DATA_PLACEHOLDER, USER_DATA_SIZE, } from '../../../constants/constants'; import { EntityReference } from '../../../generated/entity/type'; +import entityUtilClassBase from '../../../utils/EntityUtilClassBase'; import { getEntityName } from '../../../utils/EntityUtils'; import { ChipProps } from './Chip.interface'; +import './chip.less'; const Chip = ({ data, icon, + entityType, noDataPlaceholder, showNoDataPlaceholder = true, }: ChipProps) => { @@ -35,14 +39,19 @@ const Chip = ({ ); const getChipElement = (item: EntityReference) => ( - - {icon} - - {getEntityName(item)} - + + + {icon} + + {getEntityName(item)} + + ); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/Chip/Chip.interface.ts b/openmetadata-ui/src/main/resources/ui/src/components/common/Chip/Chip.interface.ts index 21b4af366f7a..819a6d03b800 100644 --- a/openmetadata-ui/src/main/resources/ui/src/components/common/Chip/Chip.interface.ts +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/Chip/Chip.interface.ts @@ -10,10 +10,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { EntityType } from '../../../enums/entity.enum'; import { EntityReference } from '../../../generated/entity/type'; export interface ChipProps { data: EntityReference[]; + entityType: EntityType; icon?: React.ReactElement; noDataPlaceholder?: string; showNoDataPlaceholder?: boolean; diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/Chip/Chip.test.tsx b/openmetadata-ui/src/main/resources/ui/src/components/common/Chip/Chip.test.tsx new file mode 100644 index 000000000000..812df3f4f2f7 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/Chip/Chip.test.tsx @@ -0,0 +1,86 @@ +/* + * Copyright 2024 Collate. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import { fireEvent, render, screen } from '@testing-library/react'; +import React from 'react'; +import { + NO_DATA_PLACEHOLDER, + USER_DATA_SIZE, +} from '../../../constants/constants'; +import { EntityType } from '../../../enums/entity.enum'; +import { MOCK_USER_ROLE } from '../../../mocks/User.mock'; +import Chip from './Chip.component'; + +const mockLinkButton = jest.fn(); + +jest.mock('react-router-dom', () => ({ + Link: jest.fn().mockImplementation(({ children, ...rest }) => ( + + {children} + + )), +})); + +jest.mock('../../../utils/EntityUtils', () => ({ + getEntityName: jest.fn().mockReturnValue('getEntityName'), +})); + +jest.mock('../../../utils/EntityUtilClassBase', () => ({ + getEntityLink: jest.fn(), +})); + +const mockProps = { + data: [], + entityType: EntityType.ROLE, +}; + +describe('Test Chip Component', () => { + it('should renders errorPlaceholder in case of no data', () => { + render(); + + expect(screen.getByText(NO_DATA_PLACEHOLDER)).toBeInTheDocument(); + }); + + it('should renders noDataPlaceholder if provided', () => { + const placeholder = 'this is custom placeholder'; + + render(); + + expect(screen.getByText(placeholder)).toBeInTheDocument(); + }); + + it('should renders tag chips', () => { + render( + + ); + + expect(screen.getAllByTestId('tag-chip')).toHaveLength(5); + expect(screen.getAllByText('getEntityName')).toHaveLength(5); + }); + + it('should renders more chip button if data is more than the size', () => { + render(); + + expect(screen.getByTestId('plus-more-count')).toBeInTheDocument(); + expect(screen.getByText('+3 more')).toBeInTheDocument(); + }); + + it('should redirect the page when click on tag chip', () => { + render(); + + const tagChip = screen.getByTestId('ApplicationBotRole-link'); + + fireEvent.click(tagChip); + + expect(mockLinkButton).toHaveBeenCalledTimes(1); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/components/common/Chip/chip.less b/openmetadata-ui/src/main/resources/ui/src/components/common/Chip/chip.less new file mode 100644 index 000000000000..fe5622b66ef8 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/components/common/Chip/chip.less @@ -0,0 +1,23 @@ +/* + * Copyright 2024 Collate. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@import (reference) url('../../../styles/variables.less'); + +.chip-tag-link { + display: flex; + color: @black; + gap: 4px; + + &:hover { + color: @black; + } +} diff --git a/openmetadata-ui/src/main/resources/ui/src/mocks/User.mock.ts b/openmetadata-ui/src/main/resources/ui/src/mocks/User.mock.ts index a70288c2a26b..064f934405a5 100644 --- a/openmetadata-ui/src/main/resources/ui/src/mocks/User.mock.ts +++ b/openmetadata-ui/src/main/resources/ui/src/mocks/User.mock.ts @@ -150,3 +150,168 @@ export const USER_TEAMS = [ href: 'http://localhost:8585/api/v1/teams/9e8b7464-3f3e-4071-af05-19be142d75bc', }, ]; + +export const MOCK_USER_ROLE = [ + { + id: '37a00e0b-383c-4451-b63f-0bad4c745abc', + type: 'role', + name: 'ApplicationBotRole', + fullyQualifiedName: 'ApplicationBotRole', + description: 'Role corresponding to a Application bot.', + displayName: 'Application bot role', + deleted: false, + href: 'http://localhost:8585/api/v1/roles/37a00e0b-383c-4451-b63f-0bad4c745abc', + }, + { + id: 'afc5583c-e268-4f6c-a638-a876d04ebaa1', + type: 'role', + name: 'DataConsumer', + fullyQualifiedName: 'DataConsumer', + description: + 'Users with Data Consumer role use different data assets for their day to day work.', + displayName: 'Data Consumer', + deleted: false, + href: 'http://localhost:8585/api/v1/roles/afc5583c-e268-4f6c-a638-a876d04ebaa1', + }, + { + id: '013746ec-2159-496e-88f7-f7175a2af919', + type: 'role', + name: 'DataQualityBotRole', + fullyQualifiedName: 'DataQualityBotRole', + description: 'Role corresponding to a Data quality bot.', + displayName: 'Data quality Bot role', + deleted: false, + href: 'http://localhost:8585/api/v1/roles/013746ec-2159-496e-88f7-f7175a2af919', + }, + { + id: 'dd72bae6-1835-4ba9-9532-aaa4b648d3e8', + type: 'role', + name: 'DataSteward', + fullyQualifiedName: 'DataSteward', + description: + 'Users with Data Steward role are responsible for ensuring correctness of metadata for data assets, thereby facilitating data governance principles within the organization.', + displayName: 'Data Steward', + deleted: false, + href: 'http://localhost:8585/api/v1/roles/dd72bae6-1835-4ba9-9532-aaa4b648d3e8', + }, + { + id: '6b007040-1378-4de9-a8b0-f922fc9f4e25', + type: 'role', + name: 'IngestionBotRole', + fullyQualifiedName: 'IngestionBotRole', + description: 'Role corresponding to a Ingestion bot.', + displayName: 'Ingestion bot role', + deleted: false, + href: 'http://localhost:8585/api/v1/roles/6b007040-1378-4de9-a8b0-f922fc9f4e25', + }, + { + id: '7f8de4ae-8b08-431c-9911-8a355aa2976e', + type: 'role', + name: 'ProfilerBotRole', + fullyQualifiedName: 'ProfilerBotRole', + description: 'Role corresponding to a Profiler bot.', + displayName: 'Profiler bot role', + deleted: false, + href: 'http://localhost:8585/api/v1/roles/7f8de4ae-8b08-431c-9911-8a355aa2976e', + }, + { + id: '7082d70a-ddb2-42db-b639-3ec4c7884c52', + type: 'role', + name: 'QualityBotRole', + fullyQualifiedName: 'QualityBotRole', + description: 'Role corresponding to a Quality bot.', + displayName: 'Quality bot role', + deleted: false, + href: 'http://localhost:8585/api/v1/roles/7082d70a-ddb2-42db-b639-3ec4c7884c52', + }, + { + id: 'admin', + type: 'role', + name: 'Admin', + }, +]; + +export const UPDATED_USER_DATA = { + changeDescription: { + fieldsAdded: [], + fieldsDeleted: [], + fieldsUpdated: [], + previousVersion: 3.2, + }, + defaultPersona: { + description: 'Person-04', + displayName: 'Person-04', + fullyQualifiedName: 'Person-04', + href: 'http://localhost:8585/api/v1/personas/0430976d-092a-46c9-90a8-61c6091a6f38', + id: '0430976d-092a-46c9-90a8-61c6091a6f38', + name: 'Person-04', + type: 'persona', + }, + deleted: false, + description: '', + displayName: '', + domain: { + description: 'description', + fullyQualifiedName: 'Engineering', + href: 'http://localhost:8585/api/v1/domains/303ca53b-5050-4caa-9c4e-d4fdada76a53', + id: '303ca53b-5050-4caa-9c4e-d4fdada76a53', + inherited: true, + name: 'Engineering', + type: 'domain', + }, + email: 'admin@openmetadata.org', + fullyQualifiedName: 'admin', + href: 'http://localhost:8585/api/v1/users/7f196a28-c4fa-4579-b420-f828985e7861', + id: '7f196a28-c4fa-4579-b420-f828985e7861', + inheritedRoles: [ + { + deleted: false, + description: + 'Users with Data Consumer role use different data assets for their day to day work.', + displayName: 'Data Consumer', + fullyQualifiedName: 'DataConsumer', + href: 'http://localhost:8585/api/v1/roles/ed94fd7c-0974-4b87-9295-02b36c4c6bcd', + id: 'ed94fd7c-0974-4b87-9295-02b36c4c6bcd', + name: 'DataConsumer', + type: 'role', + }, + ], + isAdmin: false, + isBot: false, + isEmailVerified: true, + name: 'admin', + personas: [ + { + description: 'Person-04', + displayName: 'Person-04', + fullyQualifiedName: 'Person-04', + href: 'http://localhost:8585/api/v1/personas/0430976d-092a-46c9-90a8-61c6091a6f38', + id: '0430976d-092a-46c9-90a8-61c6091a6f38', + name: 'Person-04', + type: 'persona', + }, + ], + roles: [ + { + id: '7f8de4ae-8b08-431c-9911-8a355aa2976e', + name: 'ProfilerBotRole', + type: 'role', + }, + ], + teams: [ + { + deleted: false, + description: + 'Organization under which all the other team hierarchy is created', + displayName: 'Organization', + fullyQualifiedName: 'Organization', + href: 'http://localhost:8585/api/v1/teams/9e8b7464-3f3e-4071-af05-19be142d75db', + id: '9e8b7464-3f3e-4071-af05-19be142d75db', + name: 'Organization', + type: 'team', + }, + ], + updatedAt: 1698655259882, + updatedBy: 'admin', + version: 3.3, +}; 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 1c6561caf739..393710ab512f 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 @@ -111,11 +111,21 @@ const UserPage = () => { try { const response = await updateUserDetail(userData.id, jsonPatch); if (response) { + let updatedKeyData; + + if (key === 'roles') { + updatedKeyData = { + roles: response.roles, + isAdmin: response.isAdmin, + }; + } else { + updatedKeyData = { [key]: response[key] }; + } const newCurrentUserData = { ...currentUser, - [key]: response[key], + ...updatedKeyData, }; - const newUserData = { ...userData, [key]: response[key] }; + const newUserData = { ...userData, ...updatedKeyData }; if (key === 'defaultPersona') { if (isUndefined(response.defaultPersona)) { diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/UserPage/UserPage.test.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/UserPage/UserPage.test.tsx index 436d9a71b6c5..d322ed3418d1 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/UserPage/UserPage.test.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/UserPage/UserPage.test.tsx @@ -21,8 +21,9 @@ import { } from '@testing-library/react'; import React from 'react'; import { MemoryRouter } from 'react-router-dom'; +import Users from '../../components/Settings/Users/Users.component'; import { useApplicationStore } from '../../hooks/useApplicationStore'; -import { USER_DATA } from '../../mocks/User.mock'; +import { UPDATED_USER_DATA, USER_DATA } from '../../mocks/User.mock'; import { getUserByName, updateUserDetail } from '../../rest/userAPI'; import UserPage from './UserPage.component'; @@ -145,6 +146,45 @@ describe('Test the User Page', () => { expect(mockUpdateCurrentUser).toHaveBeenCalled(); }); + it('should update user isAdmin details if changes along with user', async () => { + (Users as jest.Mock).mockImplementationOnce(({ updateUserDetails }) => ( +
+ +
+ )); + + (updateUserDetail as jest.Mock).mockImplementationOnce(() => + Promise.resolve(UPDATED_USER_DATA) + ); + + await act(async () => { + render(, { wrapper: MemoryRouter }); + }); + + await act(async () => { + fireEvent.click(screen.getByText('UserComponentSaveButton')); + }); + + expect(mockUpdateCurrentUser).toHaveBeenCalledWith(UPDATED_USER_DATA); + }); + it('Should not call updateCurrentUser if user is not currentUser logged in', async () => { (useApplicationStore as unknown as jest.Mock).mockImplementation(() => ({ currentUser: { ...USER_DATA, id: '123' },