mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-11-02 03:29:03 +00:00
feat(ui): revamp user profile page (#13744)
* revamp user profile page * minor fixes * unit test fixes * added unit test for user components * added unit test for user-profile roles and minor improvement * supported unit test for inheritet roles * added unit test for user-profile-image and minor improvement * minor fixes * cypress fixes * fix the user icon and name in case of no display name * changes as per comments
This commit is contained in:
parent
d1898ffbbc
commit
962c3d6591
@ -127,6 +127,14 @@ describe('DataConsumer Edit policy should work properly', () => {
|
||||
CREDENTIALS.lastName
|
||||
).then((id) => {
|
||||
CREDENTIALS.id = id;
|
||||
|
||||
cy.clickOutside();
|
||||
|
||||
// click the collapse button to open the other details
|
||||
cy.get(
|
||||
'[data-testid="user-profile"] .ant-collapse-expand-icon > .anticon > svg'
|
||||
).click();
|
||||
|
||||
cy.get(
|
||||
'[data-testid="user-profile"] [data-testid="user-profile-inherited-roles"]'
|
||||
).should('contain', policy);
|
||||
|
||||
@ -107,6 +107,11 @@ describe('Test Add role and assign it to the user', () => {
|
||||
cy.get(`[data-testid="${userName}"]`).click();
|
||||
verifyResponseStatusCode('@userDetailsPage', 200);
|
||||
|
||||
// click the collapse button to open the other details
|
||||
cy.get(
|
||||
'[data-testid="user-profile"] .ant-collapse-expand-icon > .anticon > svg'
|
||||
).click();
|
||||
|
||||
cy.get(
|
||||
'[data-testid="user-profile"] [data-testid="user-profile-roles"]'
|
||||
).should('contain', roleName);
|
||||
|
||||
@ -1034,10 +1034,10 @@ const TeamDetailsV1 = ({
|
||||
<Collapse
|
||||
accordion
|
||||
bordered={false}
|
||||
className="site-collapse-custom-collapse"
|
||||
className="header-collapse-custom-collapse"
|
||||
expandIconPosition="end">
|
||||
<Collapse.Panel
|
||||
className="site-collapse-custom-panel"
|
||||
className="header-collapse-custom-panel"
|
||||
collapsible="icon"
|
||||
header={teamsCollapseHeader}
|
||||
key="1">
|
||||
|
||||
@ -113,17 +113,6 @@
|
||||
overflow-y: scroll;
|
||||
}
|
||||
}
|
||||
|
||||
.site-collapse-custom-collapse .site-collapse-custom-panel {
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
background: @user-profile-background;
|
||||
border: 0px;
|
||||
|
||||
.ant-collapse-content-box {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.team-assets-right-panel {
|
||||
.summary-panel-container {
|
||||
|
||||
@ -110,7 +110,7 @@ export const UserProfileIcon = () => {
|
||||
}, [profilePicture]);
|
||||
|
||||
const { userName, teams, roles, inheritedRoles, personas } = useMemo(() => {
|
||||
const userName = currentUser?.displayName ?? currentUser?.name ?? TERM_USER;
|
||||
const userName = getEntityName(currentUser) || TERM_USER;
|
||||
|
||||
return {
|
||||
userName,
|
||||
|
||||
@ -12,11 +12,13 @@
|
||||
*/
|
||||
|
||||
import {
|
||||
findByRole,
|
||||
findByTestId,
|
||||
findByText,
|
||||
queryByTestId,
|
||||
render,
|
||||
} from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import React, { ReactNode } from 'react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { mockTeamsData, mockUserData, mockUserRole } from './mocks/User.mocks';
|
||||
@ -174,7 +176,22 @@ describe('Test User Component', () => {
|
||||
container,
|
||||
'UserProfileDetails'
|
||||
);
|
||||
const UserProfileImage = await findByText(container, 'UserProfileImage');
|
||||
|
||||
expect(UserProfileDetails).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('User profile should render when open collapsible header', async () => {
|
||||
const { container } = render(
|
||||
<Users userData={mockUserData} {...mockProp} />,
|
||||
{
|
||||
wrapper: MemoryRouter,
|
||||
}
|
||||
);
|
||||
|
||||
const collapsibleButton = await findByRole(container, 'img');
|
||||
|
||||
userEvent.click(collapsibleButton);
|
||||
|
||||
const UserProfileInheritedRoles = await findByText(
|
||||
container,
|
||||
'UserProfileInheritedRoles'
|
||||
@ -183,8 +200,6 @@ describe('Test User Component', () => {
|
||||
|
||||
const UserProfileTeams = await findByText(container, 'UserProfileTeams');
|
||||
|
||||
expect(UserProfileDetails).toBeInTheDocument();
|
||||
expect(UserProfileImage).toBeInTheDocument();
|
||||
expect(UserProfileRoles).toBeInTheDocument();
|
||||
expect(UserProfileTeams).toBeInTheDocument();
|
||||
expect(UserProfileInheritedRoles).toBeInTheDocument();
|
||||
@ -212,7 +227,7 @@ describe('Test User Component', () => {
|
||||
}
|
||||
);
|
||||
|
||||
const datasetContainer = await findByTestId(container, 'table-container');
|
||||
const datasetContainer = await findByTestId(container, 'user-profile');
|
||||
|
||||
expect(datasetContainer).toBeInTheDocument();
|
||||
});
|
||||
|
||||
@ -11,9 +11,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Col, Row, Tabs, Typography } from 'antd';
|
||||
import { Col, Collapse, Row, Space, Tabs, Typography } from 'antd';
|
||||
import Card from 'antd/lib/card/Card';
|
||||
import { noop } from 'lodash';
|
||||
import { isEmpty, noop } from 'lodash';
|
||||
import { observer } from 'mobx-react';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -22,19 +22,17 @@ import { ReactComponent as PersonaIcon } from '../../assets/svg/ic-personas.svg'
|
||||
import ActivityFeedProvider from '../../components/ActivityFeed/ActivityFeedProvider/ActivityFeedProvider';
|
||||
import { ActivityFeedTab } from '../../components/ActivityFeed/ActivityFeedTab/ActivityFeedTab.component';
|
||||
import TabsLabel from '../../components/TabsLabel/TabsLabel.component';
|
||||
import {
|
||||
getUserPath,
|
||||
NO_DATA_PLACEHOLDER,
|
||||
ROUTES,
|
||||
} from '../../constants/constants';
|
||||
import { getUserPath, ROUTES } from '../../constants/constants';
|
||||
import { myDataSearchIndex } from '../../constants/Mydata.constants';
|
||||
import { EntityType } from '../../enums/entity.enum';
|
||||
import { EntityReference } from '../../generated/entity/type';
|
||||
import { useAuth } from '../../hooks/authHooks';
|
||||
import { searchData } from '../../rest/miscAPI';
|
||||
import { getEntityName } from '../../utils/EntityUtils';
|
||||
import { DEFAULT_ENTITY_PERMISSION } from '../../utils/PermissionsUtils';
|
||||
import { useAuthContext } from '../authentication/auth-provider/AuthProvider';
|
||||
import Chip from '../common/Chip/Chip.component';
|
||||
import DescriptionV1 from '../common/description/DescriptionV1';
|
||||
import PageLayoutV1 from '../containers/PageLayoutV1';
|
||||
import EntitySummaryPanel from '../Explore/EntitySummaryPanel/EntitySummaryPanel.component';
|
||||
import { EntityDetailsObjectInterface } from '../Explore/explore.interface';
|
||||
@ -47,7 +45,6 @@ import { PersonaSelectableList } from '../Persona/PersonaSelectableList/PersonaS
|
||||
import { Props, UserPageTabs } from './Users.interface';
|
||||
import './Users.style.less';
|
||||
import UserProfileDetails from './UsersProfile/UserProfileDetails/UserProfileDetails.component';
|
||||
import UserProfileImage from './UsersProfile/UserProfileImage/UserProfileImage.component';
|
||||
import UserProfileInheritedRoles from './UsersProfile/UserProfileInheritedRoles/UserProfileInheritedRoles.component';
|
||||
import UserProfileRoles from './UsersProfile/UserProfileRoles/UserProfileRoles.component';
|
||||
import UserProfileTeams from './UsersProfile/UserProfileTeams/UserProfileTeams.component';
|
||||
@ -66,18 +63,22 @@ const Users = ({
|
||||
const location = useLocation();
|
||||
const { currentUser } = useAuthContext();
|
||||
|
||||
const isSelfProfileView = userData?.id === currentUser?.id;
|
||||
|
||||
const [previewAsset, setPreviewAsset] =
|
||||
useState<EntityDetailsObjectInterface>();
|
||||
|
||||
const [isDescriptionEdit, setIsDescriptionEdit] = useState(false);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const defaultPersona = useMemo(() => {
|
||||
return userData.personas?.find(
|
||||
(persona) => persona.id === userData.defaultPersona?.id
|
||||
);
|
||||
}, [userData]);
|
||||
const isLoggedInUser = useMemo(
|
||||
() => username === currentUser?.name,
|
||||
[username]
|
||||
);
|
||||
|
||||
const hasEditPermission = useMemo(
|
||||
() => isAdminUser || isLoggedInUser,
|
||||
[isAdminUser, isLoggedInUser]
|
||||
);
|
||||
|
||||
const fetchAssetsCount = async (query: string) => {
|
||||
try {
|
||||
@ -111,13 +112,6 @@ const Users = ({
|
||||
[updateUserDetails, userData]
|
||||
);
|
||||
|
||||
const handleDefaultPersonaUpdate = useCallback(
|
||||
async (defaultPersona?: EntityReference) => {
|
||||
await updateUserDetails({ ...userData, defaultPersona });
|
||||
},
|
||||
[updateUserDetails, userData]
|
||||
);
|
||||
|
||||
const tabDataRender = useCallback(
|
||||
(props: {
|
||||
queryFilter: string;
|
||||
@ -212,6 +206,63 @@ const Users = ({
|
||||
[activeTab, userData, username, setPreviewAsset, tabDataRender]
|
||||
);
|
||||
|
||||
const handleDescriptionChange = useCallback(
|
||||
async (description: string) => {
|
||||
await updateUserDetails({ description });
|
||||
|
||||
setIsDescriptionEdit(false);
|
||||
},
|
||||
[updateUserDetails, setIsDescriptionEdit]
|
||||
);
|
||||
|
||||
const descriptionRenderComponent = useMemo(
|
||||
() =>
|
||||
hasEditPermission ? (
|
||||
<DescriptionV1
|
||||
description={userData.description ?? ''}
|
||||
entityName={getEntityName(userData as unknown as EntityReference)}
|
||||
entityType={EntityType.USER}
|
||||
hasEditAccess={hasEditPermission}
|
||||
isEdit={isDescriptionEdit}
|
||||
showCommentsIcon={false}
|
||||
onCancel={() => setIsDescriptionEdit(false)}
|
||||
onDescriptionEdit={() => setIsDescriptionEdit(true)}
|
||||
onDescriptionUpdate={handleDescriptionChange}
|
||||
/>
|
||||
) : (
|
||||
<Space direction="vertical" size="middle">
|
||||
<Typography.Text className="right-panel-label">
|
||||
{t('label.description')}
|
||||
</Typography.Text>
|
||||
<Typography.Paragraph className="m-b-0">
|
||||
{isEmpty(userData.description)
|
||||
? t('label.no-entity', {
|
||||
entity: t('label.description'),
|
||||
})
|
||||
: userData.description}
|
||||
</Typography.Paragraph>
|
||||
</Space>
|
||||
),
|
||||
[
|
||||
userData,
|
||||
isAdminUser,
|
||||
isDescriptionEdit,
|
||||
hasEditPermission,
|
||||
getEntityName,
|
||||
handleDescriptionChange,
|
||||
]
|
||||
);
|
||||
|
||||
const userProfileCollapseHeader = useMemo(
|
||||
() => (
|
||||
<UserProfileDetails
|
||||
updateUserDetails={updateUserDetails}
|
||||
userData={userData}
|
||||
/>
|
||||
),
|
||||
[userData, updateUserDetails]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if ([UserPageTabs.MY_DATA, UserPageTabs.FOLLOWING].includes(activeTab)) {
|
||||
fetchAssetsCount(
|
||||
@ -224,97 +275,72 @@ const Users = ({
|
||||
|
||||
return (
|
||||
<PageLayoutV1 className="user-layout h-full" pageTitle={t('label.user')}>
|
||||
<div data-testid="table-container">
|
||||
<Row className="user-profile-container" data-testid="user-profile">
|
||||
<Col className="flex-center border-right" span={4}>
|
||||
<UserProfileImage
|
||||
userData={{
|
||||
id: userData.id,
|
||||
name: userData.name,
|
||||
displayName: userData.displayName,
|
||||
images: userData.profile?.images,
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col className="p-x-sm border-right" span={5}>
|
||||
<UserProfileDetails
|
||||
updateUserDetails={updateUserDetails}
|
||||
userData={{
|
||||
email: userData.email,
|
||||
name: userData.name,
|
||||
displayName: userData.displayName,
|
||||
description: userData.description,
|
||||
}}
|
||||
/>
|
||||
</Col>
|
||||
<Col className="p-x-sm border-right" span={5}>
|
||||
<UserProfileTeams
|
||||
teams={userData.teams}
|
||||
updateUserDetails={updateUserDetails}
|
||||
/>
|
||||
</Col>
|
||||
<Col className="p-x-sm border-right" span={5}>
|
||||
<div className="d-flex flex-col justify-between h-full">
|
||||
<UserProfileRoles
|
||||
isUserAdmin={userData.isAdmin}
|
||||
updateUserDetails={updateUserDetails}
|
||||
userRoles={userData.roles}
|
||||
/>
|
||||
<UserProfileInheritedRoles
|
||||
inheritedRoles={userData.inheritedRoles}
|
||||
/>
|
||||
</div>
|
||||
</Col>
|
||||
<Col className="p-x-sm border-right" span={5}>
|
||||
<div className="d-flex flex-col justify-between h-full">
|
||||
<Card
|
||||
className="ant-card-feed relative card-body-border-none card-padding-y-0 m-b-md"
|
||||
title={
|
||||
<Typography.Text
|
||||
className="right-panel-label items-center d-flex gap-2"
|
||||
data-testid="inherited-roles">
|
||||
{t('label.persona')}
|
||||
<PersonaSelectableList
|
||||
multiSelect
|
||||
hasPermission={Boolean(isAdminUser)}
|
||||
selectedPersonas={userData.personas ?? []}
|
||||
onUpdate={handlePersonaUpdate}
|
||||
<div data-testid="user-profile">
|
||||
<Collapse
|
||||
accordion
|
||||
bordered={false}
|
||||
className="header-collapse-custom-collapse user-profile-container"
|
||||
expandIconPosition="end">
|
||||
<Collapse.Panel
|
||||
className="header-collapse-custom-panel"
|
||||
collapsible="icon"
|
||||
header={userProfileCollapseHeader}
|
||||
key="1">
|
||||
<Row className="border-top p-y-lg" gutter={[0, 24]}>
|
||||
<Col span={24}>
|
||||
<Row data-testid="user-profile-details">
|
||||
<Col className="p-x-sm border-right" span={6}>
|
||||
<UserProfileTeams
|
||||
teams={userData.teams}
|
||||
updateUserDetails={updateUserDetails}
|
||||
/>
|
||||
</Typography.Text>
|
||||
}>
|
||||
<Chip
|
||||
showNoDataPlaceholder
|
||||
data={userData.personas ?? []}
|
||||
icon={<PersonaIcon height={18} />}
|
||||
noDataPlaceholder={t('message.no-persona-assigned')}
|
||||
/>
|
||||
</Card>
|
||||
<Card
|
||||
className="ant-card-feed relative card-body-border-none card-padding-y-0"
|
||||
title={
|
||||
<Typography.Text
|
||||
className="right-panel-label m-b-0 d-flex gap-2"
|
||||
data-testid="inherited-roles">
|
||||
{t('label.default-persona')}
|
||||
<PersonaSelectableList
|
||||
hasPermission={isAdminUser || isSelfProfileView}
|
||||
multiSelect={false}
|
||||
personaList={userData.personas}
|
||||
selectedPersonas={defaultPersona ? [defaultPersona] : []}
|
||||
onUpdate={handleDefaultPersonaUpdate}
|
||||
</Col>
|
||||
<Col className="p-x-sm border-right" span={6}>
|
||||
<UserProfileRoles
|
||||
updateUserDetails={updateUserDetails}
|
||||
userRoles={userData.roles}
|
||||
/>
|
||||
</Typography.Text>
|
||||
}>
|
||||
<Chip
|
||||
showNoDataPlaceholder
|
||||
data={defaultPersona ? [defaultPersona] : []}
|
||||
icon={<PersonaIcon height={18} />}
|
||||
noDataPlaceholder={NO_DATA_PLACEHOLDER}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
<Col className="p-x-sm border-right" span={6}>
|
||||
<UserProfileInheritedRoles
|
||||
inheritedRoles={userData.inheritedRoles}
|
||||
/>
|
||||
</Col>
|
||||
<Col className="p-x-sm" span={6}>
|
||||
<div className="d-flex flex-col justify-between h-full">
|
||||
<Card
|
||||
className="ant-card-feed relative card-body-border-none card-padding-y-0"
|
||||
title={
|
||||
<Typography.Text
|
||||
className="right-panel-label items-center d-flex gap-2"
|
||||
data-testid="inherited-roles">
|
||||
{t('label.persona')}
|
||||
<PersonaSelectableList
|
||||
multiSelect
|
||||
hasPermission={Boolean(isAdminUser)}
|
||||
selectedPersonas={userData.personas ?? []}
|
||||
onUpdate={handlePersonaUpdate}
|
||||
/>
|
||||
</Typography.Text>
|
||||
}>
|
||||
<Chip
|
||||
showNoDataPlaceholder
|
||||
data={userData.personas ?? []}
|
||||
icon={<PersonaIcon height={14} />}
|
||||
noDataPlaceholder={t('message.no-persona-assigned')}
|
||||
/>
|
||||
</Card>
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
</Col>
|
||||
<Col className="border-top p-lg p-b-0" span={24}>
|
||||
{descriptionRenderComponent}
|
||||
</Col>
|
||||
</Row>
|
||||
</Collapse.Panel>
|
||||
</Collapse>
|
||||
|
||||
<Tabs
|
||||
destroyInactiveTabPane
|
||||
activeKey={activeTab ?? UserPageTabs.ACTIVITY}
|
||||
|
||||
@ -30,17 +30,14 @@
|
||||
}
|
||||
|
||||
.user-layout {
|
||||
.ant-col {
|
||||
> .ant-col {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.user-profile-container {
|
||||
padding: 30px 0;
|
||||
background: @user-profile-background;
|
||||
|
||||
.profile-image-container {
|
||||
width: 190px;
|
||||
height: 190px;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
border: 3px solid @white;
|
||||
|
||||
@ -11,21 +11,20 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Button, Col, Input, Row, Space, Typography } from 'antd';
|
||||
import { Button, Divider, Input, Space, Typography } from 'antd';
|
||||
import { AxiosError } from 'axios';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { ReactComponent as EditIcon } from '../../../../assets/svg/edit-new.svg';
|
||||
import { useAuthContext } from '../../../../components/authentication/auth-provider/AuthProvider';
|
||||
import DescriptionV1 from '../../../../components/common/description/DescriptionV1';
|
||||
import InlineEdit from '../../../../components/InlineEdit/InlineEdit.component';
|
||||
import ChangePasswordForm from '../../../../components/Users/ChangePasswordForm';
|
||||
import {
|
||||
DE_ACTIVE_COLOR,
|
||||
ICON_DIMENSION,
|
||||
NO_DATA_PLACEHOLDER,
|
||||
} from '../../../../constants/constants';
|
||||
import { EntityType } from '../../../../enums/entity.enum';
|
||||
import {
|
||||
ChangePasswordRequest,
|
||||
RequestType,
|
||||
@ -36,6 +35,9 @@ import { useAuth } from '../../../../hooks/authHooks';
|
||||
import { changePassword } from '../../../../rest/auth-API';
|
||||
import { getEntityName } from '../../../../utils/EntityUtils';
|
||||
import { showErrorToast, showSuccessToast } from '../../../../utils/ToastUtils';
|
||||
import Chip from '../../../common/Chip/Chip.component';
|
||||
import { PersonaSelectableList } from '../../../Persona/PersonaSelectableList/PersonaSelectableList.component';
|
||||
import UserProfileImage from '../UserProfileImage/UserProfileImage.component';
|
||||
import { UserProfileDetailsProps } from './UserProfileDetails.interface';
|
||||
|
||||
const UserProfileDetails = ({
|
||||
@ -52,7 +54,11 @@ const UserProfileDetails = ({
|
||||
const [isChangePassword, setIsChangePassword] = useState<boolean>(false);
|
||||
const [displayName, setDisplayName] = useState(userData.displayName);
|
||||
const [isDisplayNameEdit, setIsDisplayNameEdit] = useState(false);
|
||||
const [isDescriptionEdit, setIsDescriptionEdit] = useState(false);
|
||||
|
||||
const isSelfProfileView = useMemo(
|
||||
() => userData?.id === currentUser?.id,
|
||||
[userData, currentUser]
|
||||
);
|
||||
|
||||
const isAuthProviderBasic = useMemo(
|
||||
() =>
|
||||
@ -71,28 +77,38 @@ const UserProfileDetails = ({
|
||||
[isAdminUser, isLoggedInUser]
|
||||
);
|
||||
|
||||
const onDisplayNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const hasPersonaEditPermission = useMemo(
|
||||
() => isAdminUser || isSelfProfileView,
|
||||
[isAdminUser, isSelfProfileView]
|
||||
);
|
||||
|
||||
const showChangePasswordComponent = useMemo(
|
||||
() => isAuthProviderBasic && hasEditPermission,
|
||||
[isAuthProviderBasic, hasEditPermission]
|
||||
);
|
||||
|
||||
const defaultPersona = useMemo(
|
||||
() =>
|
||||
userData.personas?.find(
|
||||
(persona) => persona.id === userData.defaultPersona?.id
|
||||
),
|
||||
[userData]
|
||||
);
|
||||
|
||||
const onDisplayNameChange = (e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
setDisplayName(e.target.value);
|
||||
};
|
||||
|
||||
const handleDescriptionChange = async (description: string) => {
|
||||
await updateUserDetails({ description });
|
||||
|
||||
setIsDescriptionEdit(false);
|
||||
};
|
||||
|
||||
const handleDisplayNameSave = () => {
|
||||
const handleDisplayNameSave = useCallback(() => {
|
||||
if (displayName !== userData.displayName) {
|
||||
updateUserDetails({ displayName: displayName ?? '' });
|
||||
}
|
||||
setIsDisplayNameEdit(false);
|
||||
};
|
||||
}, [userData.displayName, displayName, updateUserDetails]);
|
||||
|
||||
const displayNameRenderComponent = useMemo(
|
||||
() =>
|
||||
isDisplayNameEdit && hasEditPermission ? (
|
||||
<InlineEdit
|
||||
direction="vertical"
|
||||
onCancel={() => setIsDisplayNameEdit(false)}
|
||||
onSave={handleDisplayNameSave}>
|
||||
<Input
|
||||
@ -107,32 +123,31 @@ const UserProfileDetails = ({
|
||||
/>
|
||||
</InlineEdit>
|
||||
) : (
|
||||
<Row align="middle" wrap={false}>
|
||||
<Col flex="auto">
|
||||
<Typography.Text
|
||||
className="text-lg font-medium"
|
||||
ellipsis={{ tooltip: true }}>
|
||||
{hasEditPermission
|
||||
? userData.displayName ||
|
||||
t('label.add-entity', { entity: t('label.display-name') })
|
||||
: getEntityName(userData)}
|
||||
</Typography.Text>
|
||||
</Col>
|
||||
<Col className="d-flex justify-end" flex="25px">
|
||||
{hasEditPermission && (
|
||||
<EditIcon
|
||||
className="cursor-pointer"
|
||||
color={DE_ACTIVE_COLOR}
|
||||
data-testid="edit-displayName"
|
||||
{...ICON_DIMENSION}
|
||||
onClick={() => setIsDisplayNameEdit(true)}
|
||||
/>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
<Space align="center">
|
||||
<Typography.Text
|
||||
className="font-medium text-md"
|
||||
data-testid="user-name"
|
||||
ellipsis={{ tooltip: true }}
|
||||
style={{ maxWidth: '400px' }}>
|
||||
{hasEditPermission
|
||||
? userData.displayName ||
|
||||
t('label.add-entity', { entity: t('label.display-name') })
|
||||
: getEntityName(userData)}
|
||||
</Typography.Text>
|
||||
{hasEditPermission && (
|
||||
<EditIcon
|
||||
className="cursor-pointer align-middle"
|
||||
color={DE_ACTIVE_COLOR}
|
||||
data-testid="edit-displayName"
|
||||
{...ICON_DIMENSION}
|
||||
onClick={() => setIsDisplayNameEdit(true)}
|
||||
/>
|
||||
)}
|
||||
</Space>
|
||||
),
|
||||
[
|
||||
userData,
|
||||
displayName,
|
||||
isDisplayNameEdit,
|
||||
hasEditPermission,
|
||||
getEntityName,
|
||||
@ -141,48 +156,12 @@ const UserProfileDetails = ({
|
||||
]
|
||||
);
|
||||
|
||||
const descriptionRenderComponent = useMemo(
|
||||
() =>
|
||||
hasEditPermission ? (
|
||||
<DescriptionV1
|
||||
reduceDescription
|
||||
description={userData.description ?? ''}
|
||||
entityName={getEntityName(userData as unknown as EntityReference)}
|
||||
entityType={EntityType.USER}
|
||||
hasEditAccess={isAdminUser}
|
||||
isEdit={isDescriptionEdit}
|
||||
showCommentsIcon={false}
|
||||
onCancel={() => setIsDescriptionEdit(false)}
|
||||
onDescriptionEdit={() => setIsDescriptionEdit(true)}
|
||||
onDescriptionUpdate={handleDescriptionChange}
|
||||
/>
|
||||
) : (
|
||||
<Typography.Paragraph className="m-b-0">
|
||||
{userData.description ?? (
|
||||
<span className="text-grey-muted">
|
||||
{t('label.no-entity', {
|
||||
entity: t('label.description'),
|
||||
})}
|
||||
</span>
|
||||
)}
|
||||
</Typography.Paragraph>
|
||||
),
|
||||
[
|
||||
userData,
|
||||
isAdminUser,
|
||||
isDescriptionEdit,
|
||||
hasEditPermission,
|
||||
getEntityName,
|
||||
handleDescriptionChange,
|
||||
]
|
||||
);
|
||||
|
||||
const changePasswordRenderComponent = useMemo(
|
||||
() =>
|
||||
isAuthProviderBasic &&
|
||||
(isAdminUser || isLoggedInUser) && (
|
||||
showChangePasswordComponent && (
|
||||
<Button
|
||||
className="w-full text-xs"
|
||||
data-testid="change-password-button"
|
||||
type="primary"
|
||||
onClick={() => setIsChangePassword(true)}>
|
||||
{t('label.change-entity', {
|
||||
@ -190,7 +169,7 @@ const UserProfileDetails = ({
|
||||
})}
|
||||
</Button>
|
||||
),
|
||||
[isAuthProviderBasic, isAdminUser, isLoggedInUser]
|
||||
[showChangePasswordComponent]
|
||||
);
|
||||
|
||||
const handleChangePassword = async (data: ChangePasswordRequest) => {
|
||||
@ -216,34 +195,93 @@ const UserProfileDetails = ({
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Space
|
||||
className="p-sm w-full"
|
||||
data-testid="user-profile-details"
|
||||
direction="vertical"
|
||||
size="middle">
|
||||
<Space className="w-full" direction="vertical" size={2}>
|
||||
{displayNameRenderComponent}
|
||||
<Typography.Paragraph
|
||||
className="m-b-0 text-grey-muted"
|
||||
ellipsis={{ tooltip: true }}>
|
||||
const userEmailRender = useMemo(
|
||||
() => (
|
||||
<Space align="center">
|
||||
<Typography.Text
|
||||
className="text-grey-muted"
|
||||
data-testid="user-email-label">{`${t(
|
||||
'label.email'
|
||||
)} :`}</Typography.Text>
|
||||
|
||||
<Typography.Paragraph className="m-b-0" data-testid="user-email-value">
|
||||
{userData.email}
|
||||
</Typography.Paragraph>
|
||||
</Space>
|
||||
),
|
||||
[userData.email]
|
||||
);
|
||||
|
||||
const handleDefaultPersonaUpdate = useCallback(
|
||||
async (defaultPersona?: EntityReference) => {
|
||||
await updateUserDetails({ ...userData, defaultPersona });
|
||||
},
|
||||
[updateUserDetails, userData]
|
||||
);
|
||||
|
||||
const defaultPersonaRender = useMemo(
|
||||
() => (
|
||||
<Space align="center">
|
||||
<Typography.Text
|
||||
className="text-grey-muted"
|
||||
data-testid="default-persona-label">
|
||||
{`${t('label.default-persona')} :`}
|
||||
</Typography.Text>
|
||||
|
||||
<Chip
|
||||
showNoDataPlaceholder
|
||||
data={defaultPersona ? [defaultPersona] : []}
|
||||
noDataPlaceholder={NO_DATA_PLACEHOLDER}
|
||||
/>
|
||||
|
||||
<PersonaSelectableList
|
||||
hasPermission={hasPersonaEditPermission}
|
||||
multiSelect={false}
|
||||
personaList={userData.personas}
|
||||
selectedPersonas={defaultPersona ? [defaultPersona] : []}
|
||||
onUpdate={handleDefaultPersonaUpdate}
|
||||
/>
|
||||
</Space>
|
||||
),
|
||||
[
|
||||
userData.personas,
|
||||
hasPersonaEditPermission,
|
||||
defaultPersona,
|
||||
handleDefaultPersonaUpdate,
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Space
|
||||
wrap
|
||||
className="w-full justify-between"
|
||||
data-testid="user-profile-details"
|
||||
size="middle">
|
||||
<Space className="w-full">
|
||||
<UserProfileImage userData={userData} />
|
||||
{displayNameRenderComponent}
|
||||
<Divider type="vertical" />
|
||||
|
||||
{userEmailRender}
|
||||
<Divider type="vertical" />
|
||||
|
||||
{defaultPersonaRender}
|
||||
</Space>
|
||||
|
||||
<Space direction="vertical" size="middle">
|
||||
{descriptionRenderComponent}
|
||||
{changePasswordRenderComponent}
|
||||
</Space>
|
||||
|
||||
<ChangePasswordForm
|
||||
isLoading={isLoading}
|
||||
isLoggedInUser={isLoggedInUser}
|
||||
visible={isChangePassword}
|
||||
onCancel={() => setIsChangePassword(false)}
|
||||
onSave={(data) => handleChangePassword(data)}
|
||||
/>
|
||||
</Space>
|
||||
{showChangePasswordComponent && (
|
||||
<ChangePasswordForm
|
||||
isLoading={isLoading}
|
||||
isLoggedInUser={isLoggedInUser}
|
||||
visible={isChangePassword}
|
||||
onCancel={() => setIsChangePassword(false)}
|
||||
onSave={(data) => handleChangePassword(data)}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -14,11 +14,6 @@
|
||||
import { User } from '../../../../generated/entity/teams/user';
|
||||
|
||||
export interface UserProfileDetailsProps {
|
||||
userData: {
|
||||
email: string;
|
||||
name: string;
|
||||
displayName?: string;
|
||||
description?: string;
|
||||
};
|
||||
userData: User;
|
||||
updateUserDetails: (data: Partial<User>) => Promise<void>;
|
||||
}
|
||||
|
||||
@ -0,0 +1,200 @@
|
||||
/*
|
||||
* Copyright 2023 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 { MemoryRouter } from 'react-router-dom';
|
||||
import { AuthProvider } from '../../../../generated/settings/settings';
|
||||
import { useAuth } from '../../../../hooks/authHooks';
|
||||
import { USER_DATA } from '../../../../mocks/User.mock';
|
||||
import { useAuthContext } from '../../../authentication/auth-provider/AuthProvider';
|
||||
import UserProfileDetails from './UserProfileDetails.component';
|
||||
import { UserProfileDetailsProps } from './UserProfileDetails.interface';
|
||||
|
||||
const mockParams = {
|
||||
fqn: 'test',
|
||||
};
|
||||
|
||||
const mockPropsData: UserProfileDetailsProps = {
|
||||
userData: USER_DATA,
|
||||
updateUserDetails: jest.fn(),
|
||||
};
|
||||
|
||||
jest.mock('react-router-dom', () => ({
|
||||
...jest.requireActual('react-router-dom'),
|
||||
useParams: jest.fn().mockImplementation(() => mockParams),
|
||||
}));
|
||||
|
||||
jest.mock(
|
||||
'../../../../components/authentication/auth-provider/AuthProvider',
|
||||
() => ({
|
||||
useAuthContext: jest.fn(() => ({
|
||||
authConfig: {
|
||||
provider: AuthProvider.Basic,
|
||||
},
|
||||
currentUser: {
|
||||
name: 'test',
|
||||
},
|
||||
})),
|
||||
})
|
||||
);
|
||||
|
||||
jest.mock('../../../../hooks/authHooks', () => ({
|
||||
useAuth: jest.fn().mockReturnValue({ isAdminUser: true }),
|
||||
}));
|
||||
|
||||
jest.mock('../../../../utils/EntityUtils', () => ({
|
||||
getEntityName: jest.fn().mockReturnValue('entityName'),
|
||||
}));
|
||||
|
||||
jest.mock('../../../../utils/ToastUtils', () => ({
|
||||
showErrorToast: jest.fn(),
|
||||
showSuccessToast: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../UserProfileImage/UserProfileImage.component', () => {
|
||||
return jest.fn().mockReturnValue(<p>ProfilePicture</p>);
|
||||
});
|
||||
|
||||
jest.mock('../../../../components/InlineEdit/InlineEdit.component', () => {
|
||||
return jest.fn().mockReturnValue(<p>InlineEdit</p>);
|
||||
});
|
||||
|
||||
jest.mock('../../../../components/Users/ChangePasswordForm', () => {
|
||||
return jest.fn().mockReturnValue(<p>ChangePasswordForm</p>);
|
||||
});
|
||||
|
||||
jest.mock(
|
||||
'../../../Persona/PersonaSelectableList/PersonaSelectableList.component',
|
||||
() => ({
|
||||
PersonaSelectableList: jest
|
||||
.fn()
|
||||
.mockReturnValue(<p>PersonaSelectableList</p>),
|
||||
})
|
||||
);
|
||||
|
||||
jest.mock('../../../common/Chip/Chip.component', () => {
|
||||
return jest.fn().mockReturnValue(<p>Chip</p>);
|
||||
});
|
||||
|
||||
jest.mock('../UserProfileImage/UserProfileImage.component', () => {
|
||||
return jest.fn().mockReturnValue(<p>UserProfileImage</p>);
|
||||
});
|
||||
|
||||
jest.mock('../../../../rest/auth-API', () => ({
|
||||
changePassword: jest.fn(),
|
||||
}));
|
||||
|
||||
describe('Test User Profile Details Component', () => {
|
||||
it('Should render user profile details component', async () => {
|
||||
render(<UserProfileDetails {...mockPropsData} />, {
|
||||
wrapper: MemoryRouter,
|
||||
});
|
||||
|
||||
expect(screen.getByTestId('user-profile-details')).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByText('ChangePasswordForm')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Should render user data component', async () => {
|
||||
render(<UserProfileDetails {...mockPropsData} />, {
|
||||
wrapper: MemoryRouter,
|
||||
});
|
||||
|
||||
expect(screen.getByTestId('user-profile-details')).toBeInTheDocument();
|
||||
|
||||
// if user doesn't have displayname
|
||||
expect(screen.getByTestId('user-name')).toContainHTML('label.add-entity');
|
||||
expect(screen.getByTestId('edit-displayName')).toBeInTheDocument();
|
||||
|
||||
// user email
|
||||
expect(screen.getByTestId('user-email-label')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('user-email-value')).toContainHTML(
|
||||
USER_DATA.email
|
||||
);
|
||||
|
||||
// user default persona along with edit
|
||||
expect(screen.getByTestId('default-persona-label')).toBeInTheDocument();
|
||||
expect(screen.getByText('PersonaSelectableList')).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByTestId('change-password-button')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render change password button and component in case of SSO', async () => {
|
||||
(useAuthContext as jest.Mock).mockImplementationOnce(() => ({
|
||||
authConfig: jest.fn().mockImplementationOnce(() => ({
|
||||
provider: AuthProvider.Google,
|
||||
})),
|
||||
}));
|
||||
|
||||
render(<UserProfileDetails {...mockPropsData} />, {
|
||||
wrapper: MemoryRouter,
|
||||
});
|
||||
|
||||
expect(
|
||||
screen.queryByTestId('change-password-button')
|
||||
).not.toBeInTheDocument();
|
||||
|
||||
expect(screen.queryByText('ChangePasswordForm')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not provide edit access if non admin user and not current user', async () => {
|
||||
(useAuth as jest.Mock).mockImplementationOnce(() => ({
|
||||
isAdminUser: false,
|
||||
}));
|
||||
|
||||
(useAuthContext as jest.Mock).mockImplementationOnce(() => ({
|
||||
currentUser: {
|
||||
name: 'admin',
|
||||
id: '1234',
|
||||
},
|
||||
}));
|
||||
|
||||
render(<UserProfileDetails {...mockPropsData} />, {
|
||||
wrapper: MemoryRouter,
|
||||
});
|
||||
|
||||
expect(screen.getByTestId('user-profile-details')).toBeInTheDocument();
|
||||
|
||||
// render user name with no edit if doesn't have edit access
|
||||
expect(screen.getByTestId('user-name')).toContainHTML('entityName');
|
||||
expect(screen.queryByTestId('edit-displayName')).not.toBeInTheDocument();
|
||||
|
||||
// render chip in case of no default persona to other user
|
||||
expect(screen.getByTestId('default-persona-label')).toBeInTheDocument();
|
||||
expect(screen.getByText('Chip')).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByText('PersonaSelectableList')).toBeInTheDocument();
|
||||
|
||||
expect(
|
||||
screen.queryByTestId('change-password-button')
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render edit display name input on click', async () => {
|
||||
render(<UserProfileDetails {...mockPropsData} />, {
|
||||
wrapper: MemoryRouter,
|
||||
});
|
||||
|
||||
expect(screen.getByTestId('user-profile-details')).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByTestId('user-name')).toContainHTML('label.add-entity');
|
||||
|
||||
const editButton = screen.getByTestId('edit-displayName');
|
||||
|
||||
expect(editButton).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(editButton);
|
||||
|
||||
expect(screen.getByText('InlineEdit')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@ -21,7 +21,7 @@ import {
|
||||
import { UserProfileImageProps } from './UserProfileImage.interface';
|
||||
|
||||
const UserProfileImage = ({ userData }: UserProfileImageProps) => {
|
||||
const [isImgUrlValid, SetIsImgUrlValid] = useState<boolean>(true);
|
||||
const [isImgUrlValid, setIsImgUrlValid] = useState<boolean>(true);
|
||||
|
||||
const image = useMemo(
|
||||
() =>
|
||||
@ -30,31 +30,32 @@ const UserProfileImage = ({ userData }: UserProfileImageProps) => {
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (image) {
|
||||
SetIsImgUrlValid(true);
|
||||
}
|
||||
setIsImgUrlValid(Boolean(image));
|
||||
}, [image]);
|
||||
|
||||
return (
|
||||
<div className="profile-image-container">
|
||||
<div
|
||||
className="profile-image-container"
|
||||
data-testid="profile-image-container">
|
||||
{isImgUrlValid ? (
|
||||
<Image
|
||||
alt="profile"
|
||||
data-testid="user-profile-image"
|
||||
preview={false}
|
||||
referrerPolicy="no-referrer"
|
||||
src={image ?? ''}
|
||||
onError={() => {
|
||||
SetIsImgUrlValid(false);
|
||||
setIsImgUrlValid(false);
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<ProfilePicture
|
||||
displayName={userData?.displayName ?? userData.name}
|
||||
height="186"
|
||||
height="54"
|
||||
id={userData?.id ?? ''}
|
||||
name={userData?.name ?? ''}
|
||||
textClass="text-5xl"
|
||||
width=""
|
||||
textClass="text-xl"
|
||||
width="54"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2023 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 { act, render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { getImageWithResolutionAndFallback } from '../../../../utils/ProfilerUtils';
|
||||
import { mockUserData } from '../../mocks/User.mocks';
|
||||
import UserProfileImage from './UserProfileImage.component';
|
||||
import { UserProfileImageProps } from './UserProfileImage.interface';
|
||||
|
||||
const mockPropsData: UserProfileImageProps = {
|
||||
userData: mockUserData,
|
||||
};
|
||||
|
||||
jest.mock('../../../../utils/ProfilerUtils', () => ({
|
||||
getImageWithResolutionAndFallback: jest.fn().mockImplementation(undefined),
|
||||
ImageQuality: jest.fn().mockReturnValue('6x'),
|
||||
}));
|
||||
|
||||
jest.mock('../../../../components/common/ProfilePicture/ProfilePicture', () => {
|
||||
return jest.fn().mockReturnValue(<p>ProfilePicture</p>);
|
||||
});
|
||||
|
||||
describe('Test User User Profile Image Component', () => {
|
||||
it('should render user profile image component', async () => {
|
||||
render(<UserProfileImage {...mockPropsData} />);
|
||||
|
||||
expect(screen.getByTestId('profile-image-container')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render user profile picture component if no image found', async () => {
|
||||
await act(async () => {
|
||||
render(<UserProfileImage {...mockPropsData} />);
|
||||
});
|
||||
|
||||
expect(screen.getByTestId('profile-image-container')).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByText('ProfilePicture')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render user profile picture component with image', async () => {
|
||||
(getImageWithResolutionAndFallback as jest.Mock).mockImplementationOnce(
|
||||
() => '/image/test/png'
|
||||
);
|
||||
|
||||
render(<UserProfileImage {...mockPropsData} />);
|
||||
|
||||
expect(screen.getByTestId('profile-image-container')).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByTestId('user-profile-image')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@ -31,7 +31,7 @@ const UserProfileInheritedRoles = ({
|
||||
title={
|
||||
<Typography.Text
|
||||
className="right-panel-label m-b-0"
|
||||
data-testid="inherited-roles">
|
||||
data-testid="inherited-roles-label">
|
||||
{t('label.inherited-role-plural')}
|
||||
</Typography.Text>
|
||||
}>
|
||||
|
||||
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright 2023 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 { render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import UserProfileInheritedRoles from './UserProfileInheritedRoles.component';
|
||||
import { UserProfileInheritedRolesProps } from './UserProfileInheritedRoles.interface';
|
||||
|
||||
const mockPropsData: UserProfileInheritedRolesProps = {
|
||||
inheritedRoles: [],
|
||||
};
|
||||
|
||||
jest.mock('../../../common/Chip/Chip.component', () => {
|
||||
return jest.fn().mockReturnValue(<p>Chip</p>);
|
||||
});
|
||||
|
||||
describe('Test User Profile Roles Component', () => {
|
||||
it('should render user profile roles component', async () => {
|
||||
render(<UserProfileInheritedRoles {...mockPropsData} />);
|
||||
|
||||
expect(
|
||||
screen.getByTestId('user-profile-inherited-roles')
|
||||
).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByTestId('inherited-roles-label')).toBeInTheDocument();
|
||||
|
||||
expect(await screen.findAllByText('Chip')).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
@ -34,7 +34,6 @@ import { showErrorToast } from '../../../../utils/ToastUtils';
|
||||
import { UserProfileRolesProps } from './UserProfileRoles.interface';
|
||||
|
||||
const UserProfileRoles = ({
|
||||
isUserAdmin,
|
||||
userRoles,
|
||||
updateUserDetails,
|
||||
}: UserProfileRolesProps) => {
|
||||
@ -53,7 +52,7 @@ const UserProfileRoles = ({
|
||||
value: role.id,
|
||||
}));
|
||||
|
||||
if (!isUserAdmin) {
|
||||
if (!isAdminUser) {
|
||||
options.push({
|
||||
label: TERM_ADMIN,
|
||||
value: toLower(TERM_ADMIN),
|
||||
@ -61,7 +60,7 @@ const UserProfileRoles = ({
|
||||
}
|
||||
|
||||
return options;
|
||||
}, [roles]);
|
||||
}, [roles, isAdminUser, getEntityName]);
|
||||
|
||||
const fetchRoles = async () => {
|
||||
setIsRolesLoading(true);
|
||||
@ -112,27 +111,27 @@ const UserProfileRoles = ({
|
||||
() => (
|
||||
<Chip
|
||||
data={[
|
||||
...(isUserAdmin
|
||||
...(isAdminUser
|
||||
? [{ id: 'admin', type: 'role', name: TERM_ADMIN }]
|
||||
: []),
|
||||
...(userRoles ?? []),
|
||||
]}
|
||||
icon={<UserIcons height={20} />}
|
||||
noDataPlaceholder={t('message.no-roles-assigned')}
|
||||
showNoDataPlaceholder={!isUserAdmin}
|
||||
showNoDataPlaceholder={!isAdminUser}
|
||||
/>
|
||||
),
|
||||
[userRoles, isUserAdmin]
|
||||
[userRoles, isAdminUser]
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const defaultUserRoles = [
|
||||
...(userRoles?.map((role) => role.id) ?? []),
|
||||
...(isUserAdmin ? [toLower(TERM_ADMIN)] : []),
|
||||
...(isAdminUser ? [toLower(TERM_ADMIN)] : []),
|
||||
];
|
||||
|
||||
setSelectedRoles(defaultUserRoles);
|
||||
}, [isUserAdmin, userRoles]);
|
||||
}, [isAdminUser, userRoles]);
|
||||
|
||||
useEffect(() => {
|
||||
if (isRolesEdit && isEmpty(roles)) {
|
||||
@ -154,7 +153,7 @@ const UserProfileRoles = ({
|
||||
<EditIcon
|
||||
className="cursor-pointer align-middle"
|
||||
color={DE_ACTIVE_COLOR}
|
||||
data-testid="edit-roles"
|
||||
data-testid="edit-roles-button"
|
||||
{...ICON_DIMENSION}
|
||||
onClick={() => setIsRolesEdit(true)}
|
||||
/>
|
||||
|
||||
@ -13,7 +13,6 @@
|
||||
import { User } from '../../../../generated/entity/teams/user';
|
||||
|
||||
export interface UserProfileRolesProps {
|
||||
isUserAdmin?: boolean;
|
||||
userRoles: User['roles'];
|
||||
updateUserDetails: (data: Partial<User>) => Promise<void>;
|
||||
}
|
||||
|
||||
@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright 2023 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 { useAuth } from '../../../../hooks/authHooks';
|
||||
import { getRoles } from '../../../../rest/rolesAPIV1';
|
||||
import { mockUserRole } from '../../mocks/User.mocks';
|
||||
import UserProfileRoles from './UserProfileRoles.component';
|
||||
import { UserProfileRolesProps } from './UserProfileRoles.interface';
|
||||
|
||||
const mockPropsData: UserProfileRolesProps = {
|
||||
userRoles: [],
|
||||
updateUserDetails: jest.fn(),
|
||||
};
|
||||
|
||||
jest.mock('../../../../hooks/authHooks', () => ({
|
||||
useAuth: jest.fn().mockReturnValue({ isAdminUser: false }),
|
||||
}));
|
||||
|
||||
jest.mock('../../../../components/InlineEdit/InlineEdit.component', () => {
|
||||
return jest.fn().mockReturnValue(<p>InlineEdit</p>);
|
||||
});
|
||||
|
||||
jest.mock('../../../common/Chip/Chip.component', () => {
|
||||
return jest.fn().mockReturnValue(<p>Chip</p>);
|
||||
});
|
||||
|
||||
jest.mock('../../../../utils/EntityUtils', () => ({
|
||||
getEntityName: jest.fn().mockReturnValue('roleName'),
|
||||
}));
|
||||
|
||||
jest.mock('../../../../utils/ToastUtils', () => ({
|
||||
showErrorToast: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../../../rest/rolesAPIV1', () => ({
|
||||
getRoles: jest.fn().mockImplementation(() => Promise.resolve(mockUserRole)),
|
||||
}));
|
||||
|
||||
describe('Test User Profile Roles Component', () => {
|
||||
it('should render user profile roles component', async () => {
|
||||
render(<UserProfileRoles {...mockPropsData} />);
|
||||
|
||||
expect(screen.getByTestId('user-profile-roles')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render chip component', async () => {
|
||||
render(<UserProfileRoles {...mockPropsData} />);
|
||||
|
||||
expect(screen.getByTestId('user-profile-roles')).toBeInTheDocument();
|
||||
|
||||
expect(await screen.findAllByText('Chip')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should not render roles edit button if non admin user', async () => {
|
||||
render(<UserProfileRoles {...mockPropsData} />);
|
||||
|
||||
expect(screen.getByTestId('user-profile-roles')).toBeInTheDocument();
|
||||
|
||||
expect(screen.queryByTestId('edit-roles-button')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render edit button if admin user', async () => {
|
||||
(useAuth as jest.Mock).mockImplementation(() => ({
|
||||
isAdminUser: true,
|
||||
}));
|
||||
|
||||
render(<UserProfileRoles {...mockPropsData} />);
|
||||
|
||||
expect(screen.getByTestId('user-profile-roles')).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByTestId('edit-roles-button')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render select field on edit button action', async () => {
|
||||
(useAuth as jest.Mock).mockImplementation(() => ({
|
||||
isAdminUser: true,
|
||||
}));
|
||||
|
||||
render(<UserProfileRoles {...mockPropsData} />);
|
||||
|
||||
expect(screen.getByTestId('user-profile-roles')).toBeInTheDocument();
|
||||
|
||||
const editButton = screen.getByTestId('edit-roles-button');
|
||||
|
||||
expect(editButton).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(editButton);
|
||||
|
||||
expect(screen.getByText('InlineEdit')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should call roles api on edit button action', async () => {
|
||||
(useAuth as jest.Mock).mockImplementation(() => ({
|
||||
isAdminUser: true,
|
||||
}));
|
||||
|
||||
render(<UserProfileRoles {...mockPropsData} />);
|
||||
|
||||
expect(screen.getByTestId('user-profile-roles')).toBeInTheDocument();
|
||||
|
||||
const editButton = screen.getByTestId('edit-roles-button');
|
||||
|
||||
expect(editButton).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(editButton);
|
||||
|
||||
expect(getRoles).toHaveBeenCalledWith('', undefined, undefined, false, 50);
|
||||
|
||||
expect(screen.getByText('InlineEdit')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@ -64,6 +64,7 @@ const UserProfileTeams = ({
|
||||
return (
|
||||
<Card
|
||||
className="relative card-body-border-none card-padding-y-0"
|
||||
data-testid="user-team-card-container"
|
||||
key="teams-card"
|
||||
title={
|
||||
<Space align="center">
|
||||
@ -75,30 +76,28 @@ const UserProfileTeams = ({
|
||||
<EditIcon
|
||||
className="cursor-pointer align-middle"
|
||||
color={DE_ACTIVE_COLOR}
|
||||
data-testid="edit-teams"
|
||||
data-testid="edit-teams-button"
|
||||
{...ICON_DIMENSION}
|
||||
onClick={() => setIsTeamsEdit(true)}
|
||||
/>
|
||||
)}
|
||||
</Space>
|
||||
}>
|
||||
<div className="m-b-md">
|
||||
{isTeamsEdit && isAdminUser ? (
|
||||
<InlineEdit
|
||||
direction="vertical"
|
||||
onCancel={() => setIsTeamsEdit(false)}
|
||||
onSave={handleTeamsSave}>
|
||||
<TeamsSelectable
|
||||
filterJoinable
|
||||
maxValueCount={4}
|
||||
selectedTeams={selectedTeams}
|
||||
onSelectionChange={setSelectedTeams}
|
||||
/>
|
||||
</InlineEdit>
|
||||
) : (
|
||||
teamsRenderElement
|
||||
)}
|
||||
</div>
|
||||
{isTeamsEdit && isAdminUser ? (
|
||||
<InlineEdit
|
||||
direction="vertical"
|
||||
onCancel={() => setIsTeamsEdit(false)}
|
||||
onSave={handleTeamsSave}>
|
||||
<TeamsSelectable
|
||||
filterJoinable
|
||||
maxValueCount={4}
|
||||
selectedTeams={selectedTeams}
|
||||
onSelectionChange={setSelectedTeams}
|
||||
/>
|
||||
</InlineEdit>
|
||||
) : (
|
||||
teamsRenderElement
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright 2023 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 { useAuth } from '../../../../hooks/authHooks';
|
||||
import { USER_DATA, USER_TEAMS } from '../../../../mocks/User.mock';
|
||||
import UserProfileTeams from './UserProfileTeams.component';
|
||||
import { UserProfileTeamsProps } from './UserProfileTeams.interface';
|
||||
|
||||
const mockPropsData: UserProfileTeamsProps = {
|
||||
teams: [],
|
||||
updateUserDetails: jest.fn(),
|
||||
};
|
||||
|
||||
jest.mock('../../../../hooks/authHooks', () => ({
|
||||
useAuth: jest.fn().mockReturnValue({ isAdminUser: true }),
|
||||
}));
|
||||
|
||||
jest.mock('../../../../utils/CommonUtils', () => ({
|
||||
getNonDeletedTeams: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('../../../../components/InlineEdit/InlineEdit.component', () => {
|
||||
return jest.fn().mockReturnValue(<p>InlineEdit</p>);
|
||||
});
|
||||
|
||||
jest.mock('../../../common/Chip/Chip.component', () => {
|
||||
return jest.fn().mockReturnValue(<p>Chip</p>);
|
||||
});
|
||||
|
||||
jest.mock('../../../../components/TeamsSelectable/TeamsSelectable', () => {
|
||||
return jest.fn().mockReturnValue(<p>TeamsSelectable</p>);
|
||||
});
|
||||
|
||||
describe('Test User Profile Teams Component', () => {
|
||||
it('should render user profile teams component', async () => {
|
||||
render(<UserProfileTeams {...mockPropsData} />);
|
||||
|
||||
expect(screen.getByTestId('user-team-card-container')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should render teams if data available', async () => {
|
||||
render(<UserProfileTeams {...mockPropsData} teams={USER_DATA.teams} />);
|
||||
|
||||
expect(screen.getByTestId('user-team-card-container')).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByTestId('edit-teams-button')).toBeInTheDocument();
|
||||
|
||||
expect(await screen.findAllByText('Chip')).toHaveLength(1);
|
||||
});
|
||||
|
||||
it('should render teams select input on edit click', async () => {
|
||||
render(<UserProfileTeams {...mockPropsData} teams={USER_DATA.teams} />);
|
||||
|
||||
expect(screen.getByTestId('user-team-card-container')).toBeInTheDocument();
|
||||
|
||||
const editButton = screen.getByTestId('edit-teams-button');
|
||||
|
||||
expect(editButton).toBeInTheDocument();
|
||||
|
||||
fireEvent.click(editButton);
|
||||
|
||||
expect(screen.getByText('InlineEdit')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should not render edit button to non admin user', async () => {
|
||||
(useAuth as jest.Mock).mockImplementation(() => ({
|
||||
isAdminUser: false,
|
||||
}));
|
||||
|
||||
render(<UserProfileTeams {...mockPropsData} teams={USER_TEAMS} />);
|
||||
|
||||
expect(screen.getByTestId('user-team-card-container')).toBeInTheDocument();
|
||||
|
||||
expect(screen.queryByTestId('edit-teams-button')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@ -10,10 +10,13 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { Popover, Space, Tag, Typography } from 'antd';
|
||||
import { Col, Popover, Row, Space, Tag, Typography } from 'antd';
|
||||
import { isEmpty } from 'lodash';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { USER_DATA_SIZE } from '../../../constants/constants';
|
||||
import {
|
||||
NO_DATA_PLACEHOLDER,
|
||||
USER_DATA_SIZE,
|
||||
} from '../../../constants/constants';
|
||||
import { EntityReference } from '../../../generated/entity/type';
|
||||
import { getEntityName } from '../../../utils/EntityUtils';
|
||||
import { ChipProps } from './Chip.interface';
|
||||
@ -32,15 +35,15 @@ const Chip = ({
|
||||
);
|
||||
|
||||
const getChipElement = (item: EntityReference) => (
|
||||
<div
|
||||
className="w-full d-flex items-center gap-2"
|
||||
<Col
|
||||
className="d-flex gap-1 items-center"
|
||||
data-testid={item.name}
|
||||
key={item.name}>
|
||||
{icon}
|
||||
<Typography.Text className="w-56 text-left" ellipsis={{ tooltip: true }}>
|
||||
<Typography.Text className="text-left">
|
||||
{getEntityName(item)}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</Col>
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
@ -49,14 +52,18 @@ const Chip = ({
|
||||
|
||||
if (isEmpty(data) && showNoDataPlaceholder) {
|
||||
return (
|
||||
<Typography.Paragraph className="text-grey-muted">
|
||||
{noDataPlaceholder}
|
||||
<Typography.Paragraph className="text-grey-muted m-b-0">
|
||||
{noDataPlaceholder ?? NO_DATA_PLACEHOLDER}
|
||||
</Typography.Paragraph>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Space wrap data-testid="chip-container" size={4}>
|
||||
<Row
|
||||
wrap
|
||||
className="align-middle"
|
||||
data-testid="chip-container"
|
||||
gutter={[20, 6]}>
|
||||
{data.slice(0, USER_DATA_SIZE).map(getChipElement)}
|
||||
{hasMoreElement && (
|
||||
<Popover
|
||||
@ -73,7 +80,7 @@ const Chip = ({
|
||||
} more`}</Tag>
|
||||
</Popover>
|
||||
)}
|
||||
</Space>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@ -14,7 +14,7 @@ import { EntityReference } from '../../../generated/entity/type';
|
||||
|
||||
export interface ChipProps {
|
||||
data: EntityReference[];
|
||||
icon: React.ReactElement;
|
||||
noDataPlaceholder: string;
|
||||
icon?: React.ReactElement;
|
||||
noDataPlaceholder?: string;
|
||||
showNoDataPlaceholder?: boolean;
|
||||
}
|
||||
|
||||
@ -49,12 +49,12 @@ import mode from '../assets/img/service-icon-mode.png';
|
||||
import mongodb from '../assets/img/service-icon-mongodb.png';
|
||||
import msAzure from '../assets/img/service-icon-ms-azure.png';
|
||||
import mssql from '../assets/img/service-icon-mssql.png';
|
||||
import mstr from '../assets/img/service-icon-mstr.png';
|
||||
import nifi from '../assets/img/service-icon-nifi.png';
|
||||
import oracle from '../assets/img/service-icon-oracle.png';
|
||||
import pinot from '../assets/img/service-icon-pinot.png';
|
||||
import postgres from '../assets/img/service-icon-post.png';
|
||||
import powerbi from '../assets/img/service-icon-power-bi.png';
|
||||
import mstr from '../assets/img/service-icon-mstr.png';
|
||||
import prefect from '../assets/img/service-icon-prefect.png';
|
||||
import presto from '../assets/img/service-icon-presto.png';
|
||||
import pulsar from '../assets/img/service-icon-pulsar.png';
|
||||
|
||||
@ -49,7 +49,7 @@ export const LOGGED_IN_USER_STORAGE_KEY = 'loggedInUsers';
|
||||
export const ACTIVE_DOMAIN_STORAGE_KEY = 'activeDomain';
|
||||
export const DEFAULT_DOMAIN_VALUE = 'All Domains';
|
||||
|
||||
export const USER_DATA_SIZE = 4;
|
||||
export const USER_DATA_SIZE = 5;
|
||||
export const INITIAL_PAGING_VALUE = 1;
|
||||
export const JSON_TAB_SIZE = 2;
|
||||
export const PAGE_SIZE = 10;
|
||||
|
||||
142
openmetadata-ui/src/main/resources/ui/src/mocks/User.mock.ts
Normal file
142
openmetadata-ui/src/main/resources/ui/src/mocks/User.mock.ts
Normal file
@ -0,0 +1,142 @@
|
||||
/*
|
||||
* Copyright 2023 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 { User } from '../generated/entity/teams/user';
|
||||
|
||||
export const USER_DATA: User = {
|
||||
id: '7f196a28-c4fa-4579-b420-f828985e7861',
|
||||
name: 'admin',
|
||||
fullyQualifiedName: 'admin',
|
||||
description: '',
|
||||
displayName: '',
|
||||
version: 3.3,
|
||||
updatedAt: 1698655259882,
|
||||
updatedBy: 'admin',
|
||||
email: 'admin@openmetadata.org',
|
||||
href: 'http://localhost:8585/api/v1/users/7f196a28-c4fa-4579-b420-f828985e7861',
|
||||
isBot: false,
|
||||
isAdmin: true,
|
||||
teams: [
|
||||
{
|
||||
id: '9e8b7464-3f3e-4071-af05-19be142d75db',
|
||||
type: 'team',
|
||||
name: 'Organization',
|
||||
fullyQualifiedName: 'Organization',
|
||||
description:
|
||||
'Organization under which all the other team hierarchy is created',
|
||||
displayName: 'Organization',
|
||||
deleted: false,
|
||||
href: 'http://localhost:8585/api/v1/teams/9e8b7464-3f3e-4071-af05-19be142d75db',
|
||||
},
|
||||
],
|
||||
personas: [
|
||||
{
|
||||
id: '0430976d-092a-46c9-90a8-61c6091a6f38',
|
||||
type: 'persona',
|
||||
name: 'Person-04',
|
||||
fullyQualifiedName: 'Person-04',
|
||||
description: 'Person-04',
|
||||
displayName: 'Person-04',
|
||||
href: 'http://localhost:8585/api/v1/personas/0430976d-092a-46c9-90a8-61c6091a6f38',
|
||||
},
|
||||
],
|
||||
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',
|
||||
},
|
||||
changeDescription: {
|
||||
fieldsAdded: [],
|
||||
fieldsUpdated: [],
|
||||
fieldsDeleted: [],
|
||||
previousVersion: 3.2,
|
||||
},
|
||||
deleted: false,
|
||||
roles: [
|
||||
{
|
||||
id: 'ed94fd7c-0974-4b87-9295-02b36c4c6bcd',
|
||||
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/ed94fd7c-0974-4b87-9295-02b36c4c6bcd',
|
||||
},
|
||||
{
|
||||
id: 'a24f61cc-be15-411a-aaf6-28a8c8029728',
|
||||
type: 'role',
|
||||
name: 'DataSteward',
|
||||
fullyQualifiedName: 'DataSteward',
|
||||
description: 'this is test description',
|
||||
displayName: 'Data Steward',
|
||||
deleted: false,
|
||||
href: 'http://localhost:8585/api/v1/roles/a24f61cc-be15-411a-aaf6-28a8c8029728',
|
||||
},
|
||||
{
|
||||
id: '257b6976-26c6-4397-b025-087b95e45788',
|
||||
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/257b6976-26c6-4397-b025-087b95e45788',
|
||||
},
|
||||
],
|
||||
inheritedRoles: [
|
||||
{
|
||||
id: 'ed94fd7c-0974-4b87-9295-02b36c4c6bcd',
|
||||
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/ed94fd7c-0974-4b87-9295-02b36c4c6bcd',
|
||||
},
|
||||
],
|
||||
isEmailVerified: true,
|
||||
};
|
||||
|
||||
export const USER_TEAMS = [
|
||||
{
|
||||
id: '9e8b7464-3f3e-4071-af05-19be142d75db',
|
||||
type: 'team',
|
||||
name: 'Organization',
|
||||
fullyQualifiedName: 'Organization',
|
||||
description:
|
||||
'Organization under which all the other team hierarchy is created',
|
||||
displayName: 'Organization',
|
||||
deleted: false,
|
||||
href: 'http://localhost:8585/api/v1/teams/9e8b7464-3f3e-4071-af05-19be142d75db',
|
||||
},
|
||||
|
||||
{
|
||||
id: '9e8b7464-3f3e-4071-af05-19be142d75bc',
|
||||
type: 'team',
|
||||
name: 'Application',
|
||||
fullyQualifiedName: 'Application',
|
||||
description:
|
||||
'Application under which all the other team hierarchy is created',
|
||||
displayName: 'Application',
|
||||
deleted: false,
|
||||
href: 'http://localhost:8585/api/v1/teams/9e8b7464-3f3e-4071-af05-19be142d75bc',
|
||||
},
|
||||
];
|
||||
@ -745,3 +745,18 @@ a[href].link-text-grey,
|
||||
width: 36px;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
// Collapse able headers
|
||||
|
||||
.header-collapse-custom-collapse .header-collapse-custom-panel {
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
background: @user-profile-background;
|
||||
border: 0px;
|
||||
|
||||
.ant-collapse-content {
|
||||
.ant-collapse-content-box {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user