mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-08-20 06:58:18 +00:00
This commit is contained in:
parent
83ab679a13
commit
a977aa90bc
@ -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', {
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
@ -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 || []),
|
||||
`${
|
||||
|
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user