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:
Ashish Gupta 2023-11-01 11:30:55 +05:30 committed by GitHub
parent d1898ffbbc
commit 962c3d6591
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 1039 additions and 292 deletions

View File

@ -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);

View File

@ -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);

View File

@ -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">

View File

@ -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 {

View File

@ -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,

View File

@ -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();
});

View File

@ -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}

View File

@ -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;

View File

@ -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)}
/>
)}
</>
);
};

View File

@ -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>;
}

View File

@ -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();
});
});

View File

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

View File

@ -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();
});
});

View File

@ -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>
}>

View File

@ -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);
});
});

View File

@ -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)}
/>

View File

@ -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>;
}

View File

@ -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();
});
});

View File

@ -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>
);
};

View File

@ -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();
});
});

View File

@ -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>
);
};

View File

@ -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;
}

View File

@ -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';

View File

@ -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;

View 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',
},
];

View File

@ -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;
}
}
}