mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-18 15:33:02 +00:00
move pagination state entirely to URL params
don't duplicate it state variables
This commit is contained in:
@@ -1,55 +1,20 @@
|
|||||||
export const initialState = {
|
export const initialState = {
|
||||||
user_data: undefined,
|
user_data: undefined,
|
||||||
user_page: { offset: 0, limit: window.api_page_limit || 100 },
|
user_page: undefined,
|
||||||
name_filter: "",
|
|
||||||
groups_data: undefined,
|
groups_data: undefined,
|
||||||
groups_page: { offset: 0, limit: window.api_page_limit || 100 },
|
groups_page: undefined,
|
||||||
limit: window.api_page_limit || 100,
|
limit: window.api_page_limit || 100,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const reducers = (state = initialState, action) => {
|
export const reducers = (state = initialState, action) => {
|
||||||
switch (action.type) {
|
switch (action.type) {
|
||||||
// Updates the client user model data and stores the page
|
// Updates the client user model data and stores the page
|
||||||
case "USER_OFFSET":
|
|
||||||
return Object.assign({}, state, {
|
|
||||||
user_page: Object.assign({}, state.user_page, {
|
|
||||||
offset: action.value.offset,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
case "USER_LIMIT":
|
|
||||||
return Object.assign({}, state, {
|
|
||||||
user_page: Object.assign({}, state.user_page, {
|
|
||||||
limit: action.value.limit,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
case "USER_NAME_FILTER":
|
|
||||||
// set offset to 0 if name filter changed,
|
|
||||||
// otherwise leave it alone
|
|
||||||
const newOffset =
|
|
||||||
action.value.name_filter !== state.name_filter ? 0 : state.name_filter;
|
|
||||||
return Object.assign({}, state, {
|
|
||||||
user_page: Object.assign({}, state.user_page, {
|
|
||||||
offset: newOffset,
|
|
||||||
}),
|
|
||||||
name_filter: action.value.name_filter,
|
|
||||||
});
|
|
||||||
|
|
||||||
case "USER_PAGE":
|
case "USER_PAGE":
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
user_page: action.value.page,
|
user_page: action.value.page,
|
||||||
user_data: action.value.data,
|
user_data: action.value.data,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Updates the client group user model data and stores the page
|
|
||||||
case "GROUPS_OFFSET":
|
|
||||||
return Object.assign({}, state, {
|
|
||||||
groups_page: Object.assign({}, state.groups_page, {
|
|
||||||
offset: action.value.offset,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
case "GROUPS_PAGE":
|
case "GROUPS_PAGE":
|
||||||
return Object.assign({}, state, {
|
return Object.assign({}, state, {
|
||||||
groups_page: action.value.page,
|
groups_page: action.value.page,
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
import React, { useEffect } from "react";
|
import React, { useEffect } from "react";
|
||||||
import { useSelector, useDispatch } from "react-redux";
|
import { useSelector, useDispatch } from "react-redux";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import { debounce } from "lodash";
|
|
||||||
|
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { useSearchParams } from "react-router-dom-v5-compat";
|
import { useSearchParams } from "react-router-dom-v5-compat";
|
||||||
@@ -12,49 +11,15 @@ const Groups = (props) => {
|
|||||||
groups_page = useSelector((state) => state.groups_page),
|
groups_page = useSelector((state) => state.groups_page),
|
||||||
dispatch = useDispatch();
|
dispatch = useDispatch();
|
||||||
|
|
||||||
let [searchParams, setSearchParams] = useSearchParams();
|
const { setOffset, offset, setLimit, handleLimit, limit, setPagination } =
|
||||||
|
usePaginationParams();
|
||||||
var offset = parseInt(searchParams.get("offset", "0")) || 0;
|
|
||||||
var limit = parseInt(searchParams.get("limit", "0")) || window.api_page_limit;
|
|
||||||
|
|
||||||
const setOffset = (offset) => {
|
|
||||||
console.log("setting offset", offset);
|
|
||||||
if (offset < 0) {
|
|
||||||
offset = 0;
|
|
||||||
}
|
|
||||||
setSearchParams((params) => {
|
|
||||||
params.set("offset", offset);
|
|
||||||
return params;
|
|
||||||
});
|
|
||||||
dispatch({
|
|
||||||
type: "GROUPS_OFFSET",
|
|
||||||
value: {
|
|
||||||
offset: offset,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const setLimit = (newLimit) => {
|
|
||||||
if (newLimit < 1) {
|
|
||||||
newLimit = 10;
|
|
||||||
}
|
|
||||||
setSearchParams((params) => {
|
|
||||||
params.set("limit", newLimit);
|
|
||||||
return params;
|
|
||||||
});
|
|
||||||
dispatch({
|
|
||||||
type: "GROUP_LIMIT",
|
|
||||||
value: {
|
|
||||||
limit: newLimit,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
var total = groups_page ? groups_page.total : undefined;
|
var total = groups_page ? groups_page.total : undefined;
|
||||||
|
|
||||||
var { updateGroups, history } = props;
|
var { updateGroups, history } = props;
|
||||||
|
|
||||||
const dispatchPageUpdate = (data, page) => {
|
const dispatchPageUpdate = (data, page) => {
|
||||||
|
setPagination(page);
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "GROUPS_PAGE",
|
type: "GROUPS_PAGE",
|
||||||
value: {
|
value: {
|
||||||
@@ -74,10 +39,6 @@ const Groups = (props) => {
|
|||||||
return <div data-testid="no-show"></div>;
|
return <div data-testid="no-show"></div>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleLimit = debounce(async (event) => {
|
|
||||||
setLimit(event.target.value);
|
|
||||||
}, 300);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="container" data-testid="container">
|
<div className="container" data-testid="container">
|
||||||
<div className="row">
|
<div className="row">
|
||||||
|
@@ -22,6 +22,7 @@ import { FaSort, FaSortUp, FaSortDown } from "react-icons/fa";
|
|||||||
|
|
||||||
import "./server-dashboard.css";
|
import "./server-dashboard.css";
|
||||||
import { timeSince } from "../../util/timeSince";
|
import { timeSince } from "../../util/timeSince";
|
||||||
|
import { usePaginationParams } from "../../util/paginationParams";
|
||||||
import PaginationFooter from "../PaginationFooter/PaginationFooter";
|
import PaginationFooter from "../PaginationFooter/PaginationFooter";
|
||||||
|
|
||||||
const RowListItem = ({ text }) => (
|
const RowListItem = ({ text }) => (
|
||||||
@@ -32,10 +33,10 @@ RowListItem.propTypes = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const ServerDashboard = (props) => {
|
const ServerDashboard = (props) => {
|
||||||
let base_url = window.base_url || "/";
|
const base_url = window.base_url || "/";
|
||||||
let [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
// sort methods
|
// sort methods
|
||||||
var usernameDesc = (e) => e.sort((a, b) => (a.name > b.name ? 1 : -1)),
|
const usernameDesc = (e) => e.sort((a, b) => (a.name > b.name ? 1 : -1)),
|
||||||
usernameAsc = (e) => e.sort((a, b) => (a.name < b.name ? 1 : -1)),
|
usernameAsc = (e) => e.sort((a, b) => (a.name < b.name ? 1 : -1)),
|
||||||
adminDesc = (e) => e.sort((a) => (a.admin ? -1 : 1)),
|
adminDesc = (e) => e.sort((a) => (a.admin ? -1 : 1)),
|
||||||
adminAsc = (e) => e.sort((a) => (a.admin ? 1 : -1)),
|
adminAsc = (e) => e.sort((a) => (a.admin ? 1 : -1)),
|
||||||
@@ -50,20 +51,19 @@ const ServerDashboard = (props) => {
|
|||||||
runningAsc = (e) => e.sort((a) => (a.server == null ? -1 : 1)),
|
runningAsc = (e) => e.sort((a) => (a.server == null ? -1 : 1)),
|
||||||
runningDesc = (e) => e.sort((a) => (a.server == null ? 1 : -1));
|
runningDesc = (e) => e.sort((a) => (a.server == null ? 1 : -1));
|
||||||
|
|
||||||
var [errorAlert, setErrorAlert] = useState(null);
|
const [errorAlert, setErrorAlert] = useState(null);
|
||||||
var [sortMethod, setSortMethod] = useState(null);
|
const [sortMethod, setSortMethod] = useState(null);
|
||||||
var [collapseStates, setCollapseStates] = useState({});
|
const [collapseStates, setCollapseStates] = useState({});
|
||||||
|
|
||||||
var user_data = useSelector((state) => state.user_data),
|
const user_data = useSelector((state) => state.user_data);
|
||||||
user_page = useSelector((state) => state.user_page),
|
const user_page = useSelector((state) => state.user_page);
|
||||||
name_filter = useSelector((state) => state.name_filter);
|
|
||||||
|
|
||||||
// get offset, limit, name filter from URL
|
const { setOffset, offset, setLimit, handleLimit, limit, setPagination } =
|
||||||
var offset = parseInt(searchParams.get("offset", "0")) || 0;
|
usePaginationParams();
|
||||||
var limit = parseInt(searchParams.get("limit", "0")) || window.api_page_limit;
|
|
||||||
var searchNameFilter = searchParams.get("name_filter");
|
|
||||||
|
|
||||||
var total = user_page ? user_page.total : undefined;
|
const name_filter = searchParams.get("name_filter");
|
||||||
|
|
||||||
|
const total = user_page ? user_page.total : undefined;
|
||||||
|
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
@@ -79,6 +79,7 @@ const ServerDashboard = (props) => {
|
|||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
const dispatchPageUpdate = (data, page) => {
|
const dispatchPageUpdate = (data, page) => {
|
||||||
|
setPagination(page);
|
||||||
dispatch({
|
dispatch({
|
||||||
type: "USER_PAGE",
|
type: "USER_PAGE",
|
||||||
value: {
|
value: {
|
||||||
@@ -88,49 +89,15 @@ const ServerDashboard = (props) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const setOffset = (newOffset) => {
|
|
||||||
if (newOffset < 0) {
|
|
||||||
newOffset = 0;
|
|
||||||
}
|
|
||||||
setSearchParams((params) => {
|
|
||||||
params.set("offset", newOffset);
|
|
||||||
return params;
|
|
||||||
});
|
|
||||||
dispatch({
|
|
||||||
type: "USER_OFFSET",
|
|
||||||
value: {
|
|
||||||
offset: newOffset,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const setLimit = (newLimit) => {
|
|
||||||
if (newLimit < 1) {
|
|
||||||
newLimit = 10;
|
|
||||||
}
|
|
||||||
setSearchParams((params) => {
|
|
||||||
params.set("limit", newLimit);
|
|
||||||
return params;
|
|
||||||
});
|
|
||||||
dispatch({
|
|
||||||
type: "USER_LIMIT",
|
|
||||||
value: {
|
|
||||||
limit: newLimit,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const setNameFilter = (name_filter) => {
|
const setNameFilter = (name_filter) => {
|
||||||
setSearchParams((params) => {
|
setSearchParams((params) => {
|
||||||
|
if (name_filter) {
|
||||||
params.set("name_filter", name_filter);
|
params.set("name_filter", name_filter);
|
||||||
|
} else {
|
||||||
|
params.delete("name_filter");
|
||||||
|
}
|
||||||
return params;
|
return params;
|
||||||
});
|
});
|
||||||
dispatch({
|
|
||||||
type: "USER_NAME_FILTER",
|
|
||||||
value: {
|
|
||||||
name_filter: name_filter,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -139,11 +106,6 @@ const ServerDashboard = (props) => {
|
|||||||
.catch((err) => setErrorAlert("Failed to update user list."));
|
.catch((err) => setErrorAlert("Failed to update user list."));
|
||||||
}, [offset, limit, name_filter]);
|
}, [offset, limit, name_filter]);
|
||||||
|
|
||||||
if (searchNameFilter && name_filter != searchNameFilter) {
|
|
||||||
// get name_filter from URL
|
|
||||||
setNameFilter(searchNameFilter);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!user_data || !user_page) {
|
if (!user_data || !user_page) {
|
||||||
return <div data-testid="no-show"></div>;
|
return <div data-testid="no-show"></div>;
|
||||||
}
|
}
|
||||||
@@ -154,10 +116,6 @@ const ServerDashboard = (props) => {
|
|||||||
setNameFilter(event.target.value);
|
setNameFilter(event.target.value);
|
||||||
}, 300);
|
}, 300);
|
||||||
|
|
||||||
const handleLimit = debounce(async (event) => {
|
|
||||||
setLimit(event.target.value);
|
|
||||||
}, 300);
|
|
||||||
|
|
||||||
if (sortMethod != null) {
|
if (sortMethod != null) {
|
||||||
user_data = sortMethod(user_data);
|
user_data = sortMethod(user_data);
|
||||||
}
|
}
|
||||||
@@ -175,11 +133,7 @@ const ServerDashboard = (props) => {
|
|||||||
if (res.status < 300) {
|
if (res.status < 300) {
|
||||||
updateUsers(...slice)
|
updateUsers(...slice)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
dispatchPageUpdate(
|
dispatchPageUpdate(data.items, data._pagination);
|
||||||
data.items,
|
|
||||||
data._pagination,
|
|
||||||
name_filter,
|
|
||||||
);
|
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
setIsDisabled(false);
|
setIsDisabled(false);
|
||||||
@@ -537,11 +491,7 @@ const ServerDashboard = (props) => {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
updateUsers(...slice)
|
updateUsers(...slice)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
dispatchPageUpdate(
|
dispatchPageUpdate(data.items, data._pagination);
|
||||||
data.items,
|
|
||||||
data._pagination,
|
|
||||||
name_filter,
|
|
||||||
);
|
|
||||||
})
|
})
|
||||||
.catch(() =>
|
.catch(() =>
|
||||||
setErrorAlert(`Failed to update users list.`),
|
setErrorAlert(`Failed to update users list.`),
|
||||||
@@ -577,11 +527,7 @@ const ServerDashboard = (props) => {
|
|||||||
.then((res) => {
|
.then((res) => {
|
||||||
updateUsers(...slice)
|
updateUsers(...slice)
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
dispatchPageUpdate(
|
dispatchPageUpdate(data.items, data._pagination);
|
||||||
data.items,
|
|
||||||
data._pagination,
|
|
||||||
name_filter,
|
|
||||||
);
|
|
||||||
})
|
})
|
||||||
.catch(() =>
|
.catch(() =>
|
||||||
setErrorAlert(`Failed to update users list.`),
|
setErrorAlert(`Failed to update users list.`),
|
||||||
|
63
jsx/src/util/paginationParams.js
Normal file
63
jsx/src/util/paginationParams.js
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import { debounce } from "lodash";
|
||||||
|
import { useSearchParams } from "react-router-dom-v5-compat";
|
||||||
|
|
||||||
|
export const usePaginationParams = () => {
|
||||||
|
// get offset, limit, name filter from URL
|
||||||
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
|
const offset = parseInt(searchParams.get("offset", "0")) || 0;
|
||||||
|
const limit =
|
||||||
|
parseInt(searchParams.get("limit", "0")) || window.api_page_limit;
|
||||||
|
|
||||||
|
const _setOffset = (params, offset) => {
|
||||||
|
if (offset < 0) offset = 0;
|
||||||
|
if (offset === 0) {
|
||||||
|
params.delete("offset");
|
||||||
|
} else {
|
||||||
|
params.set("offset", offset);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const _setLimit = (params, limit) => {
|
||||||
|
if (limit < 10) limit = 10;
|
||||||
|
if (limit === window.api_page_limit) {
|
||||||
|
params.delete("limit");
|
||||||
|
} else {
|
||||||
|
params.set("limit", limit);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const setPagination = (pagination) => {
|
||||||
|
// update pagination in one
|
||||||
|
setSearchParams((params) => {
|
||||||
|
_setOffset(params, pagination.offset);
|
||||||
|
_setLimit(params, pagination.limit);
|
||||||
|
return params;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const setOffset = (offset) => {
|
||||||
|
if (offset < 0) offset = 0;
|
||||||
|
setSearchParams((params) => {
|
||||||
|
_setOffset(params, offset);
|
||||||
|
return params;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const setLimit = (limit) => {
|
||||||
|
setSearchParams((params) => {
|
||||||
|
_setLimit(params, limit);
|
||||||
|
return params;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleLimit = debounce(async (event) => {
|
||||||
|
setLimit(event.target.value);
|
||||||
|
}, 300);
|
||||||
|
|
||||||
|
return {
|
||||||
|
offset,
|
||||||
|
setOffset,
|
||||||
|
limit,
|
||||||
|
setLimit,
|
||||||
|
handleLimit,
|
||||||
|
setPagination,
|
||||||
|
};
|
||||||
|
};
|
Reference in New Issue
Block a user