mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-18 15:33:02 +00:00
admin: don't use state change to update offset
set offset -> request page -> response sets offset is a recipe for races instead, send request with new offset and only update offset state made easier by consolidating page update requests into single loadPageData
This commit is contained in:
@@ -41,7 +41,7 @@ const ServerDashboard = (props) => {
|
|||||||
let user_data = useSelector((state) => state.user_data);
|
let user_data = useSelector((state) => state.user_data);
|
||||||
const user_page = useSelector((state) => state.user_page);
|
const user_page = useSelector((state) => state.user_page);
|
||||||
|
|
||||||
const { setOffset, offset, setLimit, handleLimit, limit, setPagination } =
|
const { offset, setLimit, handleLimit, limit, setPagination } =
|
||||||
usePaginationParams();
|
usePaginationParams();
|
||||||
|
|
||||||
const name_filter = searchParams.get("name_filter") || "";
|
const name_filter = searchParams.get("name_filter") || "";
|
||||||
@@ -123,25 +123,38 @@ const ServerDashboard = (props) => {
|
|||||||
} else {
|
} else {
|
||||||
params.set("state", new_state_filter);
|
params.set("state", new_state_filter);
|
||||||
}
|
}
|
||||||
console.log("setting search params", params.toString());
|
|
||||||
return params;
|
return params;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// the callback to update the displayed user list
|
// the callback to update the displayed user list
|
||||||
const updateUsersWithParams = () =>
|
const updateUsersWithParams = (params) => {
|
||||||
updateUsers({
|
if (params) {
|
||||||
offset,
|
if (params.offset !== undefined && params.offset < 0) {
|
||||||
|
params.offset = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return updateUsers({
|
||||||
|
offset: offset,
|
||||||
limit,
|
limit,
|
||||||
name_filter,
|
name_filter,
|
||||||
sort,
|
sort,
|
||||||
state: state_filter,
|
state: state_filter,
|
||||||
|
...params,
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
// single callback to reload the page
|
||||||
updateUsersWithParams()
|
// uses current state, or params can be specified if state
|
||||||
|
// should be updated _after_ load, e.g. offset
|
||||||
|
const loadPageData = (params) => {
|
||||||
|
return updateUsersWithParams(params)
|
||||||
.then((data) => dispatchPageUpdate(data.items, data._pagination))
|
.then((data) => dispatchPageUpdate(data.items, data._pagination))
|
||||||
.catch((err) => setErrorAlert("Failed to update user list."));
|
.catch((err) => setErrorAlert("Failed to update user list."));
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadPageData();
|
||||||
}, [offset, limit, name_filter, sort, state_filter]);
|
}, [offset, limit, name_filter, sort, state_filter]);
|
||||||
|
|
||||||
if (!user_data || !user_page) {
|
if (!user_data || !user_page) {
|
||||||
@@ -172,14 +185,7 @@ const ServerDashboard = (props) => {
|
|||||||
action(user.name, server.name)
|
action(user.name, server.name)
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.status < 300) {
|
if (res.status < 300) {
|
||||||
updateUsersWithParams()
|
loadPageData();
|
||||||
.then((data) => {
|
|
||||||
dispatchPageUpdate(data.items, data._pagination);
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
setIsDisabled(false);
|
|
||||||
setErrorAlert(`Failed to update users list.`);
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
setErrorAlert(`Failed to ${name.toLowerCase()}.`);
|
setErrorAlert(`Failed to ${name.toLowerCase()}.`);
|
||||||
setIsDisabled(false);
|
setIsDisabled(false);
|
||||||
@@ -519,13 +525,7 @@ const ServerDashboard = (props) => {
|
|||||||
return res;
|
return res;
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
updateUsersWithParams()
|
loadPageData();
|
||||||
.then((data) => {
|
|
||||||
dispatchPageUpdate(data.items, data._pagination);
|
|
||||||
})
|
|
||||||
.catch(() =>
|
|
||||||
setErrorAlert(`Failed to update users list.`),
|
|
||||||
);
|
|
||||||
return res;
|
return res;
|
||||||
})
|
})
|
||||||
.catch(() => setErrorAlert(`Failed to start servers.`));
|
.catch(() => setErrorAlert(`Failed to start servers.`));
|
||||||
@@ -556,13 +556,7 @@ const ServerDashboard = (props) => {
|
|||||||
return res;
|
return res;
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
updateUsersWithParams()
|
loadPageData();
|
||||||
.then((data) => {
|
|
||||||
dispatchPageUpdate(data.items, data._pagination);
|
|
||||||
})
|
|
||||||
.catch(() =>
|
|
||||||
setErrorAlert(`Failed to update users list.`),
|
|
||||||
);
|
|
||||||
return res;
|
return res;
|
||||||
})
|
})
|
||||||
.catch(() => setErrorAlert(`Failed to stop servers.`));
|
.catch(() => setErrorAlert(`Failed to stop servers.`));
|
||||||
@@ -590,8 +584,13 @@ const ServerDashboard = (props) => {
|
|||||||
limit={limit}
|
limit={limit}
|
||||||
visible={user_data.length}
|
visible={user_data.length}
|
||||||
total={total}
|
total={total}
|
||||||
next={() => setOffset(offset + limit)}
|
// don't trigger via setOffset state change,
|
||||||
prev={() => setOffset(offset - limit)}
|
// which can cause infinite cycles.
|
||||||
|
// offset state will be set upon reply via setPagination
|
||||||
|
next={() => loadPageData({ offset: offset + limit })}
|
||||||
|
prev={() =>
|
||||||
|
loadPageData({ offset: limit > offset ? 0 : offset - limit })
|
||||||
|
}
|
||||||
handleLimit={handleLimit}
|
handleLimit={handleLimit}
|
||||||
/>
|
/>
|
||||||
<br></br>
|
<br></br>
|
||||||
|
@@ -608,7 +608,7 @@ test("Search for user calls updateUsers with name filter", async () => {
|
|||||||
// expect(mockUpdateUsers).toBeCalledWith(0, 100, "ab");
|
// expect(mockUpdateUsers).toBeCalledWith(0, 100, "ab");
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Interacting with PaginationFooter causes state update and refresh via useEffect call", async () => {
|
test("Interacting with PaginationFooter requests page update", async () => {
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
render(serverDashboardJsx());
|
render(serverDashboardJsx());
|
||||||
});
|
});
|
||||||
@@ -625,14 +625,10 @@ test("Interacting with PaginationFooter causes state update and refresh via useE
|
|||||||
jest.runAllTimers();
|
jest.runAllTimers();
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(searchParams.get("offset")).toEqual("2");
|
expect(mockUpdateUsers).toBeCalledWith({
|
||||||
expect(searchParams.get("limit")).toEqual("2");
|
...defaultUpdateUsersParams,
|
||||||
|
offset: 2,
|
||||||
// FIXME: should call updateUsers, does in reality.
|
});
|
||||||
// tests don't reflect reality due to mocked state/useSelector
|
|
||||||
// unclear how to fix this.
|
|
||||||
// expect(callbackSpy.mock.calls).toHaveLength(2);
|
|
||||||
// expect(callbackSpy).toHaveBeenCalledWith(2, 2, "");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Server delete button exists for named servers", async () => {
|
test("Server delete button exists for named servers", async () => {
|
||||||
|
Reference in New Issue
Block a user