fix(ui): user page scroll issue on tabs and pagination (#12424)

* fix user page scroll on tabs and pagination too

* changes as per comments

* fix unit test
This commit is contained in:
Ashish Gupta 2023-07-15 12:06:51 +05:30 committed by GitHub
parent fc29eba285
commit 00934279c6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 108 additions and 81 deletions

View File

@ -139,8 +139,7 @@ const mockProp = {
updateThreadHandler: jest.fn(), updateThreadHandler: jest.fn(),
setFeedFilter: jest.fn(), setFeedFilter: jest.fn(),
threadType: 'Task' as ThreadType.Task, threadType: 'Task' as ThreadType.Task,
onFollowingEntityPaginate: jest.fn(), handlePaginate: jest.fn(),
onOwnedEntityPaginate: jest.fn(),
onSwitchChange: jest.fn(), onSwitchChange: jest.fn(),
}; };

View File

@ -11,7 +11,17 @@
* limitations under the License. * limitations under the License.
*/ */
import { Card, Image, Input, Select, Space, Tabs, Typography } from 'antd'; import {
Card,
Col,
Image,
Input,
Row,
Select,
Space,
Tabs,
Typography,
} from 'antd';
import { ReactComponent as EditIcon } from 'assets/svg/edit-new.svg'; import { ReactComponent as EditIcon } from 'assets/svg/edit-new.svg';
import { ReactComponent as IconTeamsGrey } from 'assets/svg/teams-grey.svg'; import { ReactComponent as IconTeamsGrey } from 'assets/svg/teams-grey.svg';
import { AxiosError } from 'axios'; import { AxiosError } from 'axios';
@ -80,8 +90,7 @@ const Users = ({
isLoggedinUser, isLoggedinUser,
isAuthDisabled, isAuthDisabled,
username, username,
onFollowingEntityPaginate, handlePaginate,
onOwnedEntityPaginate,
}: Props) => { }: Props) => {
const { tab = UserPageTabs.ACTIVITY } = useParams<{ tab: UserPageTabs }>(); const { tab = UserPageTabs.ACTIVITY } = useParams<{ tab: UserPageTabs }>();
const [displayName, setDisplayName] = useState(userData.displayName); const [displayName, setDisplayName] = useState(userData.displayName);
@ -692,48 +701,42 @@ const Users = ({
} }
return ( return (
<PageLayoutV1 <Row className="user-page-layout" wrap={false}>
className="user-page-layout" <Col className="user-layout-scroll" flex="auto">
pageTitle={t('label.user')} {entityData.data.length ? (
rightPanel={ <SearchedData
showSummaryPanel && data={entityData.data ?? []}
entityDetails && ( handleSummaryPanelDisplay={handleSummaryPanelDisplay}
isFilterSelected={false}
isSummaryPanelVisible={showSummaryPanel}
selectedEntityId={entityDetails?.id || ''}
totalValue={entityData.total ?? 0}
onPaginationChange={handlePaginate}
/>
) : (
<ErrorPlaceHolder className="m-0">
<Typography.Paragraph>
{tab === UserPageTabs.MY_DATA
? t('server.you-have-not-action-anything-yet', {
action: t('label.owned-lowercase'),
})
: t('server.you-have-not-action-anything-yet', {
action: t('label.followed-lowercase'),
})}
</Typography.Paragraph>
</ErrorPlaceHolder>
)}
</Col>
{showSummaryPanel && entityDetails && (
<Col className="user-page-layout-right-panel " flex="400px">
<EntitySummaryPanel <EntitySummaryPanel
entityDetails={{ details: entityDetails }} entityDetails={{ details: entityDetails }}
handleClosePanel={handleClosePanel} handleClosePanel={handleClosePanel}
/> />
) </Col>
}
rightPanelWidth={400}>
{entityData.data.length ? (
<SearchedData
currentPage={entityData.currPage}
data={entityData.data ?? []}
handleSummaryPanelDisplay={handleSummaryPanelDisplay}
isFilterSelected={false}
isSummaryPanelVisible={showSummaryPanel}
selectedEntityId={entityDetails?.id || ''}
totalValue={entityData.total ?? 0}
onPaginationChange={
tab === UserPageTabs.MY_DATA
? onOwnedEntityPaginate
: onFollowingEntityPaginate
}
/>
) : (
<ErrorPlaceHolder className="m-0">
<Typography.Paragraph>
{tab === UserPageTabs.MY_DATA
? t('server.you-have-not-action-anything-yet', {
action: t('label.owned-lowercase'),
})
: t('server.you-have-not-action-anything-yet', {
action: t('label.followed-lowercase'),
})}
</Typography.Paragraph>
</ErrorPlaceHolder>
)} )}
</PageLayoutV1> </Row>
); );
} }
case UserPageTabs.ACTIVITY: case UserPageTabs.ACTIVITY:

View File

@ -19,21 +19,18 @@ export interface Props {
followingEntities: { followingEntities: {
data: SearchedDataProps['data']; data: SearchedDataProps['data'];
total: number; total: number;
currPage: number;
}; };
ownedEntities: { ownedEntities: {
data: SearchedDataProps['data']; data: SearchedDataProps['data'];
total: number; total: number;
currPage: number;
}; };
username: string; username: string;
isUserEntitiesLoading: boolean; isUserEntitiesLoading: boolean;
isAdminUser: boolean; isAdminUser: boolean;
isLoggedinUser: boolean; isLoggedinUser: boolean;
isAuthDisabled: boolean; isAuthDisabled: boolean;
handlePaginate: (page: string | number) => void;
updateUserDetails: (data: Partial<User>) => Promise<void>; updateUserDetails: (data: Partial<User>) => Promise<void>;
onFollowingEntityPaginate: (page: string | number) => void;
onOwnedEntityPaginate: (page: string | number) => void;
} }
export enum UserPageTabs { export enum UserPageTabs {

View File

@ -29,8 +29,12 @@
} }
.user-page-layout { .user-page-layout {
height: @users-page-tabs-height; .user-layout-scroll {
.page-layout-rightpanel { height: @users-page-tabs-height;
overflow-y: scroll;
}
.user-page-layout-right-panel {
padding-right: 0 !important; padding-right: 0 !important;
background-color: @white; background-color: @white;
border: 1px solid @border-color; border: 1px solid @border-color;

View File

@ -91,5 +91,4 @@ export interface SearchedDataProps {
entityType: string entityType: string
) => void; ) => void;
filter?: Qs.ParsedQs; filter?: Qs.ParsedQs;
currentPage?: number;
} }

View File

@ -48,7 +48,6 @@ const SearchedData: React.FC<SearchedDataProps> = ({
selectedEntityId, selectedEntityId,
handleSummaryPanelDisplay, handleSummaryPanelDisplay,
filter, filter,
currentPage,
}) => { }) => {
const searchResultCards = useMemo(() => { const searchResultCards = useMemo(() => {
return data.map(({ _source: table, highlight }, index) => { return data.map(({ _source: table, highlight }, index) => {
@ -159,11 +158,7 @@ const SearchedData: React.FC<SearchedDataProps> = ({
<Pagination <Pagination
hideOnSinglePage hideOnSinglePage
className="text-center" className="text-center"
current={ current={isNumber(Number(page)) ? Number(page) : 1}
isNumber(Number(page ?? currentPage))
? Number(page ?? currentPage)
: 1
}
pageSize={ pageSize={
size && isNumber(Number(size)) size && isNumber(Number(size))
? Number(size) ? Number(size)

View File

@ -18,10 +18,16 @@ import Users from 'components/Users/Users.component';
import { compare } from 'fast-json-patch'; import { compare } from 'fast-json-patch';
import { isEmpty } from 'lodash'; import { isEmpty } from 'lodash';
import { observer } from 'mobx-react'; import { observer } from 'mobx-react';
import { AssetsDataType } from 'Models'; import Qs from 'qs';
import React, { Dispatch, SetStateAction, useEffect, useState } from 'react'; import React, {
Dispatch,
SetStateAction,
useEffect,
useMemo,
useState,
} from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useParams } from 'react-router-dom'; import { useHistory, useParams } from 'react-router-dom';
import { searchData } from 'rest/miscAPI'; import { searchData } from 'rest/miscAPI';
import { getUserByName, updateUserDetail } from 'rest/userAPI'; import { getUserByName, updateUserDetail } from 'rest/userAPI';
import AppState from '../../AppState'; import AppState from '../../AppState';
@ -32,8 +38,10 @@ import { User } from '../../generated/entity/teams/user';
import { useAuth } from '../../hooks/authHooks'; import { useAuth } from '../../hooks/authHooks';
import { SearchEntityHits } from '../../utils/APIUtils'; import { SearchEntityHits } from '../../utils/APIUtils';
import { showErrorToast } from '../../utils/ToastUtils'; import { showErrorToast } from '../../utils/ToastUtils';
import { UserAssetsDataType } from './UserPage.interface';
const UserPage = () => { const UserPage = () => {
const history = useHistory();
const { t } = useTranslation(); const { t } = useTranslation();
const { username, tab = UserProfileTab.ACTIVITY } = const { username, tab = UserProfileTab.ACTIVITY } =
useParams<{ [key: string]: string }>(); useParams<{ [key: string]: string }>();
@ -46,17 +54,26 @@ const UserPage = () => {
const [isUserEntitiesLoading, setIsUserEntitiesLoading] = const [isUserEntitiesLoading, setIsUserEntitiesLoading] =
useState<boolean>(false); useState<boolean>(false);
const [followingEntities, setFollowingEntities] = useState<AssetsDataType>({ const [followingEntities, setFollowingEntities] =
useState<UserAssetsDataType>({
data: [],
total: 0,
});
const [ownedEntities, setOwnedEntities] = useState<UserAssetsDataType>({
data: [], data: [],
total: 0, total: 0,
currPage: 1,
});
const [ownedEntities, setOwnedEntities] = useState<AssetsDataType>({
data: [],
total: 0,
currPage: 1,
}); });
const { page = 1 } = useMemo(
() =>
Qs.parse(
location.search.startsWith('?')
? location.search.substr(1)
: location.search
),
[location.search]
);
const fetchUserData = () => { const fetchUserData = () => {
setUserData({} as User); setUserData({} as User);
getUserByName(username, 'profile,roles,teams') getUserByName(username, 'profile,roles,teams')
@ -95,16 +112,14 @@ const UserPage = () => {
const fetchEntities = async ( const fetchEntities = async (
fetchOwnedEntities = false, fetchOwnedEntities = false,
handleEntity: Dispatch<SetStateAction<AssetsDataType>> handleEntity: Dispatch<SetStateAction<UserAssetsDataType>>
) => { ) => {
const entity = fetchOwnedEntities ? ownedEntities : followingEntities;
if (userData.id) { if (userData.id) {
setIsUserEntitiesLoading(true); setIsUserEntitiesLoading(true);
try { try {
const response = await searchData( const response = await searchData(
'', '',
entity.currPage, Number(page),
PAGE_SIZE, PAGE_SIZE,
getQueryFilters(fetchOwnedEntities), getQueryFilters(fetchOwnedEntities),
'', '',
@ -118,14 +133,12 @@ const UserPage = () => {
handleEntity({ handleEntity({
data: hits, data: hits,
total, total,
currPage: entity.currPage,
}); });
} else { } else {
const total = 0; const total = 0;
handleEntity({ handleEntity({
data: [], data: [],
total, total,
currPage: entity.currPage,
}); });
} }
} catch (error) { } catch (error) {
@ -141,12 +154,10 @@ const UserPage = () => {
} }
}; };
const handleFollowingEntityPaginate = (page: string | number) => { const handleEntityPaginate = (page: string | number) => {
setFollowingEntities((pre) => ({ ...pre, currPage: page as number })); history.push({
}; search: Qs.stringify({ page }),
});
const handleOwnedEntityPaginate = (page: string | number) => {
setOwnedEntities((pre) => ({ ...pre, currPage: page as number }));
}; };
const ErrorPlaceholder = () => { const ErrorPlaceholder = () => {
@ -189,6 +200,7 @@ const UserPage = () => {
return ( return (
<Users <Users
followingEntities={followingEntities} followingEntities={followingEntities}
handlePaginate={handleEntityPaginate}
isAdminUser={Boolean(isAdminUser)} isAdminUser={Boolean(isAdminUser)}
isAuthDisabled={Boolean(isAuthDisabled)} isAuthDisabled={Boolean(isAuthDisabled)}
isLoggedinUser={isLoggedinUser(username)} isLoggedinUser={isLoggedinUser(username)}
@ -197,8 +209,6 @@ const UserPage = () => {
updateUserDetails={updateUserDetails} updateUserDetails={updateUserDetails}
userData={userData} userData={userData}
username={username} username={username}
onFollowingEntityPaginate={handleFollowingEntityPaginate}
onOwnedEntityPaginate={handleOwnedEntityPaginate}
/> />
); );
} else { } else {
@ -214,13 +224,13 @@ const UserPage = () => {
if (tab === UserProfileTab.FOLLOWING) { if (tab === UserProfileTab.FOLLOWING) {
fetchEntities(false, setFollowingEntities); fetchEntities(false, setFollowingEntities);
} }
}, [followingEntities.currPage, tab, userData]); }, [page, tab, userData]);
useEffect(() => { useEffect(() => {
if (tab === UserProfileTab.MY_DATA) { if (tab === UserProfileTab.MY_DATA) {
fetchEntities(true, setOwnedEntities); fetchEntities(true, setOwnedEntities);
} }
}, [ownedEntities.currPage, tab, userData]); }, [page, tab, userData]);
useEffect(() => { useEffect(() => {
setCurrentLoggedInUser(AppState.getCurrentUserDetails()); setCurrentLoggedInUser(AppState.getCurrentUserDetails());

View File

@ -0,0 +1,19 @@
/*
* 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 { SearchEntityHits } from 'utils/APIUtils';
export interface UserAssetsDataType {
data: SearchEntityHits;
total: number;
}

View File

@ -104,6 +104,7 @@ jest.mock('components/authentication/auth-provider/AuthProvider', () => {
}); });
jest.mock('react-router-dom', () => ({ jest.mock('react-router-dom', () => ({
useHistory: jest.fn(),
useParams: jest.fn().mockImplementation(() => ({ username: 'xyz' })), useParams: jest.fn().mockImplementation(() => ({ username: 'xyz' })),
useLocation: jest.fn().mockImplementation(() => new URLSearchParams()), useLocation: jest.fn().mockImplementation(() => new URLSearchParams()),
})); }));