Skip to content

Commit 7eb3d68

Browse files
authored
Add pagination support to attachment modal (#8953)
* Add pagination support to policy attachment modal Updated the policy attachment modal to include pagination for listing policies. Adjusted the search function and integrated a paginator for handling large datasets efficiently. This enhances usability when browsing extensive policy/group/member lists. * Remove unnecessary wrapping <div> around DataTable component. * Fix inconsistent indentation in auth forms component * Refactor policy search logic in forms and policies page Simplify state management by removing redundant `results` state and directly using API response. Centralize pagination logic and improve search behavior with a dedicated handler for prefix updates. * Refactor search functions to support pagination Replaced custom search logic with paginated API calls to improve scalability and maintainability. Updated group members, user policies, and user groups search functions for consistent use of pagination parameters. Removed unnecessary local state and redundant logic to streamline the code. * Fix inconsistent indentation in auth forms component * Add user search optimization with local caching Refactored the user search functionality to use a locally cached list of all users, reducing redundancy in API calls. Introduced a `searchUsers` function that filters from a pre-fetched user list, paginating results efficiently. * Refactor indentation in auth forms for better readability Adjusted the indentation of the DataTable and Paginator components in the auth forms file to improve code clarity and maintain consistency. This change does not alter functionality but enhances code maintainability and readability. * Refactor indentation in auth forms component for consistency Updated the indentation in the DataTable component for better readability and consistent formatting. No functional changes were introduced. * Fix indentation in auth forms component for better readability Corrected inconsistent indentation in the `rowFn` function of the auth forms component. This improves code readability and adheres to the project's formatting standards without altering functionality. * Refactor pagination and search logic for consistency Centralized page size constant and improved code readability by using shared pagination parameters. Simplified and unified the handling of search and pagination across components. * Rename `pageSize` to `PageSize` for consistency. Updated the `pageSize` constant to `PageSize` in both definition and usage to follow naming conventions. This ensures better alignment with code standards and improves readability. * Set page size using constant in search functions Replaced hardcoded page size value (5) with the `PageSize` constant across multiple components for consistency and maintainability. This ensures centralized management of pagination settings.
1 parent a363709 commit 7eb3d68

File tree

6 files changed

+59
-23
lines changed

6 files changed

+59
-23
lines changed

webui/src/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export const RefTypeBranch = 'branch';
22
export const RefTypeCommit = 'commit';
33
export const RefTypeTag = 'tag';
4+
export const PageSize = 5;
45

56
export enum TreeRowType {
67
Object,

webui/src/lib/components/auth/forms.jsx

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {SearchIcon} from "@primer/octicons-react";
88

99
import {useAPI} from "../../hooks/api";
1010
import {Checkbox, DataTable, DebouncedFormControl, AlertError, Loading} from "../controls";
11+
import {Paginator} from "../pagination";
1112

1213

1314
export const AttachModal = ({
@@ -16,17 +17,19 @@ export const AttachModal = ({
1617
filterPlaceholder = 'Filter...'
1718
}) => {
1819
const search = useRef(null);
19-
const [searchPrefix, setSearchPrefix] = useState("");
20+
const [paginationParams, setPaginationParams] = useState({ prefix: "", after: "" });
2021
const [selected, setSelected] = useState([]);
2122

23+
const { response, error, loading } = useAPI(() => {
24+
return searchFn(paginationParams.prefix, paginationParams.after);
25+
}, [paginationParams]);
26+
2227
useEffect(() => {
2328
if (!!search.current && search.current.value === "")
2429
search.current.focus();
2530
});
2631

27-
const {response, error, loading} = useAPI(() => {
28-
return searchFn(searchPrefix);
29-
}, [searchPrefix]);
32+
const nextPage = response?.pagination?.has_more ? response.pagination.next_offset : null;
3033

3134
let content;
3235
if (loading) content = <Loading/>;
@@ -37,7 +40,7 @@ export const AttachModal = ({
3740
headers={headers}
3841
keyFn={ent => ent.id}
3942
emptyState={emptyState}
40-
results={response}
43+
results={response.results}
4144
rowFn={ent => [
4245
<Checkbox
4346
defaultChecked={selected.some(selectedEnt => selectedEnt.id === ent.id)}
@@ -48,7 +51,14 @@ export const AttachModal = ({
4851
]}
4952
firstFixedCol={true}
5053
/>
51-
54+
<Paginator
55+
after={paginationParams.after}
56+
nextPage={nextPage}
57+
onPaginate={(newAfter) => setPaginationParams(prev => ({
58+
...prev,
59+
after: newAfter
60+
}))}
61+
/>
5262
<div className="mt-3">
5363
{(selected.length > 0) &&
5464
<p>
@@ -78,6 +88,14 @@ export const AttachModal = ({
7888
</>
7989
);
8090

91+
const handleSearchChange = () => {
92+
setPaginationParams(prev => ({
93+
...prev,
94+
prefix: search.current.value,
95+
after: ""
96+
}));
97+
};
98+
8199
return (
82100
<Modal show={show} onHide={onHide}>
83101
<Modal.Header closeButton>
@@ -94,9 +112,7 @@ export const AttachModal = ({
94112
<DebouncedFormControl
95113
ref={search}
96114
placeholder={filterPlaceholder}
97-
onChange={() => {
98-
setSearchPrefix(search.current.value)
99-
}}/>
115+
onChange={handleSearchChange}/>
100116
</InputGroup>
101117
</Form>
102118
<div className="mt-2">

webui/src/pages/auth/groups/group/members.jsx

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {useRouter} from "../../../../lib/hooks/router";
2121
import {Link} from "../../../../lib/components/nav";
2222
import {resolveUserDisplayName} from "../../../../lib/utils";
2323
import {allUsersFromLakeFS} from "../../../../lib/components/auth/users";
24+
import {PageSize} from "../../../../constants";
2425

2526

2627
const GroupMemberList = ({ groupId, after, onPaginate }) => {
@@ -36,14 +37,31 @@ const GroupMemberList = ({ groupId, after, onPaginate }) => {
3637
}, [refresh]);
3738

3839

39-
const searchUsers = async (prefix, maxResults, resolveUserDisplayNameFN = (user => user.id)) => {
40-
let allUsersList = allUsers;
41-
if (allUsersList.length === 0) {
42-
allUsersList = await allUsersFromLakeFS(resolveUserDisplayNameFN)
43-
setAllUsers(allUsersList)
44-
}
45-
let filteredUsers = allUsersList.filter(user => resolveUserDisplayNameFN(user).startsWith(prefix));
46-
return filteredUsers.slice(0, maxResults);
40+
useEffect(() => {
41+
const loadUsers = async () => {
42+
const users = await allUsersFromLakeFS(resolveUserDisplayName);
43+
setAllUsers(users);
44+
};
45+
void loadUsers();
46+
}, []);
47+
48+
const searchUsers = (prefix, after, resolveUserDisplayNameFN = (user => user.id)) => {
49+
const filteredUsers = allUsers.filter(user =>
50+
resolveUserDisplayNameFN(user).toLowerCase().startsWith(prefix.toLowerCase())
51+
);
52+
const startIndex = after ? parseInt(after, 10) : 0;
53+
const page = filteredUsers.slice(startIndex, startIndex + PageSize);
54+
const nextOffset = (startIndex + PageSize < filteredUsers.length)
55+
? (startIndex + PageSize).toString()
56+
: null;
57+
58+
return {
59+
results: page,
60+
pagination: {
61+
next_offset: nextOffset,
62+
has_more: nextOffset !== null
63+
}
64+
};
4765
};
4866
let content;
4967
if (loading) content = <Loading/>;
@@ -86,7 +104,7 @@ const GroupMemberList = ({ groupId, after, onPaginate }) => {
86104
modalTitle={'Add to Group'}
87105
addText={'Add to Group'}
88106
resolveEntityFn={resolveUserDisplayName}
89-
searchFn={prefix => searchUsers(prefix, 5, resolveUserDisplayName).then(res => res)}
107+
searchFn={(prefix, after) => searchUsers(prefix, after, resolveUserDisplayName)}
90108
onHide={() => setShowAddModal(false)}
91109
onAttach={(selected) => {
92110
Promise.all(selected.map(user => auth.addUserToGroup(user.id, groupId)))

webui/src/pages/auth/groups/group/policies.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
} from "../../../../lib/components/controls";
2020
import {Link} from "../../../../lib/components/nav";
2121
import {useRouter} from "../../../../lib/hooks/router";
22+
import {PageSize} from "../../../../constants";
2223

2324

2425
const GroupPoliciesList = ({ groupId, after, onPaginate }) => {
@@ -74,7 +75,7 @@ const GroupPoliciesList = ({ groupId, after, onPaginate }) => {
7475
filterPlaceholder={'Find Policy...'}
7576
modalTitle={'Attach Policies'}
7677
addText={'Attach Policies'}
77-
searchFn={prefix => auth.listPolicies(prefix, "", 5).then(res => res.results)}
78+
searchFn={(prefix, after) => auth.listPolicies(prefix, after, PageSize)}
7879
onHide={() => setShowAddModal(false)}
7980
onAttach={(selected) => {
8081
Promise.all(selected.map(policy => auth.attachPolicyToGroup(groupId, policy.id)))

webui/src/pages/auth/users/user/groups.jsx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { AttachModal } from "../../../../lib/components/auth/forms";
2020
import { ConfirmationButton } from "../../../../lib/components/modals";
2121
import { useRouter } from "../../../../lib/hooks/router";
2222
import { Link } from "../../../../lib/components/nav";
23+
import {PageSize} from "../../../../constants";
2324

2425
const resolveGroupDisplayName = (group) => {
2526
if(!group) return "";
@@ -100,9 +101,7 @@ const UserGroupsList = ({ userId, after, onPaginate }) => {
100101
modalTitle={"Add to Groups"}
101102
addText={"Add to Groups"}
102103
headers={["", "Group Name"]}
103-
searchFn={(prefix) =>
104-
auth.listGroups(prefix, "", 5).then((res) => res.results)
105-
}
104+
searchFn={(prefix, after) => auth.listGroups(prefix, after, PageSize)}
106105
resolveEntityFn={resolveGroupDisplayName}
107106
onHide={() => setShowAddModal(false)}
108107
onAttach={(selected) => {

webui/src/pages/auth/users/user/policies.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {AttachModal} from "../../../../lib/components/auth/forms";
1919
import {ConfirmationButton} from "../../../../lib/components/modals";
2020
import {Link} from "../../../../lib/components/nav";
2121
import {useRouter} from "../../../../lib/hooks/router";
22+
import {PageSize} from "../../../../constants";
2223

2324

2425
const UserPoliciesList = ({ userId, after, onPaginate }) => {
@@ -71,7 +72,7 @@ const UserPoliciesList = ({ userId, after, onPaginate }) => {
7172
filterPlaceholder={'Find Policy...'}
7273
modalTitle={'Attach Policies'}
7374
addText={'Attach Policies'}
74-
searchFn={prefix => auth.listPolicies(prefix, "", 5).then(res => res.results)}
75+
searchFn={(prefix,after) => auth.listPolicies(prefix, after, PageSize)}
7576
onHide={() => setShowAddModal(false)}
7677
onAttach={(selected) => {
7778
Promise.all(selected.map(policy => auth.attachPolicyToUser(userId, policy.id)))

0 commit comments

Comments
 (0)