Fix #4210 UI: Create User form should include Display Name of the user. (#4283)

This commit is contained in:
Sachin Chaurasiya 2022-04-20 23:32:15 +05:30 committed by GitHub
parent 83ab679a13
commit a977aa90bc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 351 additions and 27 deletions

View File

@ -42,6 +42,7 @@ const CreateUser = ({
const markdownRef = useRef<EditorContentRef>();
const [description] = useState<string>('');
const [email, setEmail] = useState('');
const [displayName, setDisplayName] = useState('');
const [isAdmin, setIsAdmin] = useState(false);
const [isBot, setIsBot] = useState(false);
const [selectedRoles, setSelectedRoles] = useState<Array<string | undefined>>(
@ -52,6 +53,7 @@ const CreateUser = ({
);
const [showErrorMsg, setShowErrorMsg] = useState({
email: false,
displayName: false,
validEmail: false,
});
@ -77,6 +79,12 @@ const CreateUser = ({
break;
case 'displayName':
setDisplayName(value);
setShowErrorMsg({ ...showErrorMsg, displayName: false });
break;
default:
break;
}
@ -166,6 +174,7 @@ const CreateUser = ({
const userProfile: CreateUserSchema = {
description: markdownRef.current?.getEditorContent() || undefined,
name: email.split('@')[0],
displayName,
roles: validRole.length ? validRole : undefined,
teams: validTeam.length ? validTeam : undefined,
email: email,
@ -223,7 +232,7 @@ const CreateUser = ({
<h6 className="tw-heading tw-text-base">Create User</h6>
<Field>
<label className="tw-block tw-form-label tw-mb-0" htmlFor="email">
{requiredField('Email:')}
{requiredField('Email')}
</label>
<input
className="tw-form-inputs tw-px-3 tw-py-1"
@ -242,14 +251,35 @@ const CreateUser = ({
? errorMsg(jsonData['form-error-messages']['invalid-email'])
: null}
</Field>
<Field>
<label className="tw-block tw-form-label tw-mb-0" htmlFor="displayName">
Display Name
</label>
<input
className="tw-form-inputs tw-px-3 tw-py-1"
data-testid="displayName"
id="displayName"
name="displayName"
placeholder="displayName"
type="text"
value={displayName}
onChange={handleValidation}
/>
{showErrorMsg.email
? errorMsg(jsonData['form-error-messages']['empty-email'])
: showErrorMsg.validEmail
? errorMsg(jsonData['form-error-messages']['invalid-email'])
: null}
</Field>
<Field>
<label className="tw-block tw-form-label tw-mb-0" htmlFor="description">
Description:
Description
</label>
<RichTextEditor initialValue={description} ref={markdownRef} />
</Field>
<Field>
<label className="tw-block tw-form-label tw-mb-0">Teams:</label>
<label className="tw-block tw-form-label tw-mb-0">Teams</label>
<DropDown
className={classNames('tw-bg-white', {
'tw-bg-gray-100 tw-cursor-not-allowed': teams.length === 0,
@ -263,7 +293,7 @@ const CreateUser = ({
</Field>
<Field>
<label className="tw-block tw-form-label tw-mb-0" htmlFor="role">
Roles:
Roles
</label>
<DropDown
className={classNames('tw-bg-white', {

View File

@ -14,6 +14,7 @@
import { findByTestId, queryByTestId, render } from '@testing-library/react';
import React from 'react';
import { MemoryRouter } from 'react-router-dom';
import { FeedFilter } from '../../enums/mydata.enum';
import Users from './Users.component';
const mockUserData = {
@ -102,25 +103,61 @@ jest.mock('../common/TabsPane/TabsPane', () => {
return jest.fn().mockReturnValue(<p data-testid="tabs">Tabs</p>);
});
jest.mock('../ActivityFeed/ActivityFeedList/ActivityFeedList.tsx', () => {
return jest.fn().mockReturnValue(<p>FeedCards</p>);
});
const mockObserve = jest.fn();
const mockunObserve = jest.fn();
window.IntersectionObserver = jest.fn().mockImplementation(() => ({
observe: mockObserve,
unobserve: mockunObserve,
}));
const mockFetchFeedHandler = jest.fn();
const feedFilterHandler = jest.fn();
const fetchData = jest.fn();
const postFeed = jest.fn();
const mockPaging = {
after: 'MTY0OTIzNTQ3MzExMg==',
total: 202,
};
const mockProp = {
feedData: [],
feedFilter: FeedFilter.ALL,
feedFilterHandler: feedFilterHandler,
fetchData: fetchData,
fetchFeedHandler: mockFetchFeedHandler,
isFeedLoading: false,
paging: mockPaging,
postFeedHandler: postFeed,
};
describe('Test User Component', () => {
it('Should render user component', async () => {
const { container } = render(<Users userData={mockUserData} />, {
wrapper: MemoryRouter,
});
const { container } = render(
<Users userData={mockUserData} {...mockProp} />,
{
wrapper: MemoryRouter,
}
);
const tabs = await findByTestId(container, 'tabs');
const noAssets = await findByTestId(container, 'no-assets');
const leftPanel = await findByTestId(container, 'left-panel');
expect(tabs).toBeInTheDocument();
expect(noAssets).toBeInTheDocument();
expect(leftPanel).toBeInTheDocument();
});
it('Should render non deleted teams', async () => {
const { container } = render(<Users userData={mockUserData} />, {
wrapper: MemoryRouter,
});
const { container } = render(
<Users userData={mockUserData} {...mockProp} />,
{
wrapper: MemoryRouter,
}
);
const teamFinance = await findByTestId(container, 'Finance');
const teamDataPlatform = await findByTestId(container, 'Data_Platform');
@ -130,12 +167,30 @@ describe('Test User Component', () => {
});
it('Should not render deleted teams', async () => {
const { container } = render(<Users userData={mockUserData} />, {
wrapper: MemoryRouter,
});
const { container } = render(
<Users userData={mockUserData} {...mockProp} />,
{
wrapper: MemoryRouter,
}
);
const deletedTeam = queryByTestId(container, 'Customer_Support');
expect(deletedTeam).not.toBeInTheDocument();
});
it('Should create an observer if IntersectionObserver is available', async () => {
const { container } = render(
<Users userData={mockUserData} {...mockProp} />,
{
wrapper: MemoryRouter,
}
);
const obServerElement = await findByTestId(container, 'observer-element');
expect(obServerElement).toBeInTheDocument();
expect(mockObserve).toHaveBeenCalled();
});
});

View File

@ -11,28 +11,77 @@
* limitations under the License.
*/
import React, { useState } from 'react';
import { EntityThread } from 'Models';
import React, { Fragment, RefObject, useEffect, useState } from 'react';
import { filterList, observerOptions } from '../../constants/Mydata.constants';
import { AssetsType } from '../../enums/entity.enum';
import { FeedFilter } from '../../enums/mydata.enum';
import { EntityReference, User } from '../../generated/entity/teams/user';
import { Paging } from '../../generated/type/paging';
import { useInfiniteScroll } from '../../hooks/useInfiniteScroll';
import UserCard from '../../pages/teams/UserCard';
import { getNonDeletedTeams } from '../../utils/CommonUtils';
import { dropdownIcon as DropDownIcon } from '../../utils/svgconstant';
import SVGIcons, { Icons } from '../../utils/SvgUtils';
import ActivityFeedList from '../ActivityFeed/ActivityFeedList/ActivityFeedList';
import { Button } from '../buttons/Button/Button';
import Avatar from '../common/avatar/Avatar';
import TabsPane from '../common/TabsPane/TabsPane';
import PageLayout from '../containers/PageLayout';
import DropDownList from '../dropdown/DropDownList';
import Loader from '../Loader/Loader';
import Onboarding from '../onboarding/Onboarding';
type Props = {
interface Props {
userData: User;
};
feedData: EntityThread[];
feedFilter: FeedFilter;
paging: Paging;
isFeedLoading: boolean;
feedFilterHandler: (v: FeedFilter) => void;
fetchFeedHandler: (filterType: FeedFilter, after?: string) => void;
postFeedHandler: (value: string, id: string) => void;
deletePostHandler?: (threadId: string, postId: string) => void;
}
const Users = ({ userData }: Props) => {
const Users = ({
userData,
feedData,
feedFilter,
feedFilterHandler,
isFeedLoading,
postFeedHandler,
deletePostHandler,
fetchFeedHandler,
paging,
}: Props) => {
const [activeTab, setActiveTab] = useState(1);
const [fieldListVisible, setFieldListVisible] = useState<boolean>(false);
const [elementRef, isInView] = useInfiniteScroll(observerOptions);
const activeTabHandler = (tab: number) => {
setActiveTab(tab);
};
const handleDropDown = (
_e: React.MouseEvent<HTMLElement, MouseEvent>,
value?: string
) => {
feedFilterHandler((value as FeedFilter) || FeedFilter.ALL);
setFieldListVisible(false);
};
const tabs = [
{
name: 'Activity Feed',
icon: {
alt: 'activity_feed',
name: 'activity_feed',
title: 'Activity Feed',
selectedName: 'activity-feed-color',
},
isProtected: false,
position: 1,
},
{
name: 'Owned Data',
icon: {
@ -42,7 +91,7 @@ const Users = ({ userData }: Props) => {
selectedName: 'owned-data',
},
isProtected: false,
position: 1,
position: 2,
},
{
name: 'Following',
@ -53,7 +102,7 @@ const Users = ({ userData }: Props) => {
selectedName: 'following',
},
isProtected: false,
position: 2,
position: 3,
},
];
@ -88,6 +137,7 @@ const Users = ({ userData }: Props) => {
</span>
</p>
<p className="tw-mt-2">{userData.email}</p>
<p className="tw-mt-2">{userData.description}</p>
</div>
<div className="tw-pb-4 tw-mb-4 tw-border-b">
<h6 className="tw-heading tw-mb-3">Teams</h6>
@ -150,6 +200,80 @@ const Users = ({ userData }: Props) => {
);
};
const getFilterDropDown = () => {
return (
<Fragment>
<div className="tw-relative tw-mt-5">
<Button
className="hover:tw-no-underline focus:tw-no-underline"
data-testid="feeds"
size="custom"
tag="button"
theme="primary"
variant="link"
onClick={() => setFieldListVisible((visible) => !visible)}>
<span className="tw-font-medium">
{filterList.find((f) => f.value === feedFilter)?.name}
</span>
<DropDownIcon />
</Button>
{fieldListVisible && (
<DropDownList
dropDownList={filterList}
value={feedFilter}
onSelect={handleDropDown}
/>
)}
</div>
</Fragment>
);
};
const getLoader = () => {
return isFeedLoading ? <Loader /> : null;
};
const getFeedTabData = () => {
return (
<Fragment>
{feedData?.length > 0 || feedFilter !== FeedFilter.ALL ? (
<Fragment>
{getFilterDropDown()}
<ActivityFeedList
withSidePanel
className=""
deletePostHandler={deletePostHandler}
feedList={feedData}
postFeedHandler={postFeedHandler}
/>
</Fragment>
) : (
<Onboarding />
)}
<div
data-testid="observer-element"
id="observer-element"
ref={elementRef as RefObject<HTMLDivElement>}>
{getLoader()}
</div>
</Fragment>
);
};
const fetchMoreFeed = (
isElementInView: boolean,
pagingObj: Paging,
isLoading: boolean
) => {
if (isElementInView && pagingObj?.after && !isLoading) {
fetchFeedHandler(feedFilter, pagingObj.after);
}
};
useEffect(() => {
fetchMoreFeed(isInView as boolean, paging, isFeedLoading);
}, [isInView, paging, isFeedLoading]);
return (
<PageLayout classes="tw-h-full tw-px-6" leftPanel={fetchLeftPanel()}>
<div className="tw-mb-3">
@ -161,14 +285,15 @@ const Users = ({ userData }: Props) => {
/>
</div>
<div>
{activeTab === 1 &&
{activeTab === 1 && getFeedTabData()}
{activeTab === 2 &&
getEntityData(
getAssets(userData?.owns || []),
`${
userData?.displayName || userData?.name || 'User'
} does not own anything yet`
)}
{activeTab === 2 &&
{activeTab === 3 &&
getEntityData(
getAssets(userData?.follows || []),
`${

View File

@ -12,21 +12,37 @@
*/
import { AxiosError, AxiosResponse } from 'axios';
import { observer } from 'mobx-react';
import { EntityThread } from 'Models';
import React, { useEffect, useState } from 'react';
import { useParams } from 'react-router-dom';
import AppState from '../../AppState';
import { getFeedsWithFilter, postFeedById } from '../../axiosAPIs/feedsAPI';
import { getUserByName } from '../../axiosAPIs/userAPI';
import PageContainerV1 from '../../components/containers/PageContainerV1';
import Loader from '../../components/Loader/Loader';
import Users from '../../components/Users/Users.component';
import {
onConfirmText,
onErrorText,
onUpdatedConversastionError,
} from '../../constants/feed.constants';
import { FeedFilter } from '../../enums/mydata.enum';
import { User } from '../../generated/entity/teams/user';
import { Paging } from '../../generated/type/paging';
import jsonData from '../../jsons/en';
import { showErrorToast } from '../../utils/ToastUtils';
import { deletePost, getUpdatedThread } from '../../utils/FeedUtils';
import { showErrorToast, showSuccessToast } from '../../utils/ToastUtils';
const UserPage = () => {
const { username } = useParams<{ [key: string]: string }>();
const [isLoading, setIsLoading] = useState(true);
const [userData, setUserData] = useState<User>({} as User);
const [isError, setIsError] = useState(false);
const [feedFilter, setFeedFilter] = useState<FeedFilter>(FeedFilter.ALL);
const [entityThread, setEntityThread] = useState<EntityThread[]>([]);
const [isFeedLoading, setIsFeedLoading] = useState<boolean>(false);
const [paging, setPaging] = useState<Paging>({} as Paging);
const fetchUserData = () => {
getUserByName(username, 'profile,roles,teams,follows,owns')
@ -63,16 +79,114 @@ const UserPage = () => {
);
};
const feedFilterHandler = (filter: FeedFilter) => {
setFeedFilter(filter);
};
const getFeedData = (filterType: FeedFilter, after?: string) => {
setIsFeedLoading(true);
const currentUserId = AppState.getCurrentUserDetails()?.id;
getFeedsWithFilter(currentUserId, filterType, after)
.then((res: AxiosResponse) => {
const { data, paging: pagingObj } = res.data;
setPaging(pagingObj);
setEntityThread((prevData) => [...prevData, ...data]);
})
.catch((err: AxiosError) => {
showErrorToast(
err,
jsonData['api-error-messages']['fetch-activity-feed-error']
);
})
.finally(() => {
setIsFeedLoading(false);
});
};
const postFeedHandler = (value: string, id: string) => {
const currentUser = AppState.userDetails?.name ?? AppState.users[0]?.name;
const data = {
message: value,
from: currentUser,
};
postFeedById(id, data)
.then((res: AxiosResponse) => {
if (res.data) {
const { id, posts } = res.data;
setEntityThread((pre) => {
return pre.map((thread) => {
if (thread.id === id) {
return { ...res.data, posts: posts.slice(-3) };
} else {
return thread;
}
});
});
}
})
.catch((err: AxiosError) => {
showErrorToast(err, jsonData['api-error-messages']['feed-post-error']);
});
};
const deletePostHandler = (threadId: string, postId: string) => {
deletePost(threadId, postId)
.then(() => {
getUpdatedThread(threadId)
.then((data) => {
setEntityThread((pre) => {
return pre.map((thread) => {
if (thread.id === data.id) {
return {
...thread,
posts: data.posts.slice(-3),
postsCount: data.postsCount,
};
} else {
return thread;
}
});
});
})
.catch((error) => {
const message = error?.message;
showErrorToast(message ?? onUpdatedConversastionError);
});
showSuccessToast(onConfirmText);
})
.catch((error) => {
const message = error?.message;
showErrorToast(message ?? onErrorText);
});
};
useEffect(() => {
fetchUserData();
}, [username]);
useEffect(() => {
getFeedData(feedFilter);
setEntityThread([]);
}, [feedFilter]);
return (
<PageContainerV1 className="tw-pt-4">
{isLoading ? (
<Loader />
) : !isError ? (
<Users userData={userData} />
<Users
deletePostHandler={deletePostHandler}
feedData={entityThread || []}
feedFilter={feedFilter}
feedFilterHandler={feedFilterHandler}
fetchFeedHandler={getFeedData}
isFeedLoading={isFeedLoading}
paging={paging}
postFeedHandler={postFeedHandler}
userData={userData}
/>
) : (
<ErrorPlaceholder />
)}
@ -80,4 +194,4 @@ const UserPage = () => {
);
};
export default UserPage;
export default observer(UserPage);