diff --git a/openmetadata-ui/src/main/resources/ui/src/components/CreateUser/CreateUser.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/CreateUser/CreateUser.component.tsx
index 37d7395e3be..da6780d5b5e 100644
--- a/openmetadata-ui/src/main/resources/ui/src/components/CreateUser/CreateUser.component.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/components/CreateUser/CreateUser.component.tsx
@@ -42,6 +42,7 @@ const CreateUser = ({
const markdownRef = useRef();
const [description] = useState('');
const [email, setEmail] = useState('');
+ const [displayName, setDisplayName] = useState('');
const [isAdmin, setIsAdmin] = useState(false);
const [isBot, setIsBot] = useState(false);
const [selectedRoles, setSelectedRoles] = useState>(
@@ -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 = ({
Create User
+
+
+
+
+ {showErrorMsg.email
+ ? errorMsg(jsonData['form-error-messages']['empty-email'])
+ : showErrorMsg.validEmail
+ ? errorMsg(jsonData['form-error-messages']['invalid-email'])
+ : null}
+
-
+
{
return jest.fn().mockReturnValue(Tabs
);
});
+jest.mock('../ActivityFeed/ActivityFeedList/ActivityFeedList.tsx', () => {
+ return jest.fn().mockReturnValue(FeedCards
);
+});
+
+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(, {
- wrapper: MemoryRouter,
- });
+ const { container } = render(
+ ,
+ {
+ 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(, {
- wrapper: MemoryRouter,
- });
+ const { container } = render(
+ ,
+ {
+ 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(, {
- wrapper: MemoryRouter,
- });
+ const { container } = render(
+ ,
+ {
+ 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(
+ ,
+ {
+ wrapper: MemoryRouter,
+ }
+ );
+
+ const obServerElement = await findByTestId(container, 'observer-element');
+
+ expect(obServerElement).toBeInTheDocument();
+
+ expect(mockObserve).toHaveBeenCalled();
+ });
});
diff --git a/openmetadata-ui/src/main/resources/ui/src/components/Users/Users.component.tsx b/openmetadata-ui/src/main/resources/ui/src/components/Users/Users.component.tsx
index 40d10127b06..a63315192dd 100644
--- a/openmetadata-ui/src/main/resources/ui/src/components/Users/Users.component.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/components/Users/Users.component.tsx
@@ -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(false);
+ const [elementRef, isInView] = useInfiniteScroll(observerOptions);
const activeTabHandler = (tab: number) => {
setActiveTab(tab);
};
+ const handleDropDown = (
+ _e: React.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) => {
{userData.email}
+ {userData.description}
Teams
@@ -150,6 +200,80 @@ const Users = ({ userData }: Props) => {
);
};
+ const getFilterDropDown = () => {
+ return (
+
+
+
+ {fieldListVisible && (
+
+ )}
+
+
+ );
+ };
+
+ const getLoader = () => {
+ return isFeedLoading ?
: null;
+ };
+
+ const getFeedTabData = () => {
+ return (
+
+ {feedData?.length > 0 || feedFilter !== FeedFilter.ALL ? (
+
+ {getFilterDropDown()}
+
+
+ ) : (
+
+ )}
+ }>
+ {getLoader()}
+
+
+ );
+ };
+
+ 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 (
@@ -161,14 +285,15 @@ const Users = ({ userData }: Props) => {
/>
- {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 || []),
`${
diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/UserPage/UserPage.component.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/UserPage/UserPage.component.tsx
index d6a00cfa269..2a475e7faa7 100644
--- a/openmetadata-ui/src/main/resources/ui/src/pages/UserPage/UserPage.component.tsx
+++ b/openmetadata-ui/src/main/resources/ui/src/pages/UserPage/UserPage.component.tsx
@@ -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
({} as User);
const [isError, setIsError] = useState(false);
+ const [feedFilter, setFeedFilter] = useState(FeedFilter.ALL);
+ const [entityThread, setEntityThread] = useState([]);
+ const [isFeedLoading, setIsFeedLoading] = useState(false);
+ const [paging, setPaging] = useState({} 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 (
{isLoading ? (
) : !isError ? (
-
+
) : (
)}
@@ -80,4 +194,4 @@ const UserPage = () => {
);
};
-export default UserPage;
+export default observer(UserPage);