Supporting user pagination on the web admin view

This commit is contained in:
Keith Stevens
2023-01-15 15:26:12 +09:00
parent 69952dbf80
commit 35e92dbd15
5 changed files with 78 additions and 16 deletions
@@ -19,14 +19,14 @@ router = APIRouter()
def get_users(
api_client_id: Optional[UUID] = None,
max_count: Optional[int] = Query(100, gt=0, le=10000),
gte: Optional[str] = None,
gt: Optional[str] = None,
lt: Optional[str] = None,
auth_method: Optional[str] = None,
api_client: ApiClient = Depends(deps.get_api_client),
db: Session = Depends(deps.get_db),
):
ur = UserRepository(db, api_client)
users = ur.query_users(api_client_id=api_client_id, limit=max_count, gte=gte, lt=lt, auth_method=auth_method)
users = ur.query_users(api_client_id=api_client_id, limit=max_count, gt=gt, lt=lt, auth_method=auth_method)
return [u.to_protocol_frontend_user() for u in users]
+3 -3
View File
@@ -162,7 +162,7 @@ class UserRepository:
self,
api_client_id: Optional[UUID] = None,
limit: Optional[int] = 20,
gte: Optional[str] = None,
gt: Optional[str] = None,
lt: Optional[str] = None,
auth_method: Optional[str] = None,
) -> list[User]:
@@ -183,8 +183,8 @@ class UserRepository:
users = users.order_by(User.display_name)
if gte:
users = users.filter(User.display_name >= gte)
if gt:
users = users.filter(User.display_name > gt)
if lt:
users = users.filter(User.display_name < lt)
+40 -5
View File
@@ -11,6 +11,7 @@ import {
Th,
Thead,
Tr,
useToast,
} from "@chakra-ui/react";
import Link from "next/link";
import { useState } from "react";
@@ -18,26 +19,60 @@ import { get } from "src/lib/api";
import type { User } from "src/types/Users";
import useSWR from "swr";
interface Pagination {
/**
* The user's `display_name` used for pagination.
*/
cursor: string;
/**
* The pagination direction.
*/
direction: "forward" | "back";
}
/**
* Fetches users from the users api route and then presents them in a simple Chakra table.
*/
const UsersCell = () => {
const [pageIndex, setPageIndex] = useState(0);
const toast = useToast();
const [pagination, setPagination] = useState<Pagination>({ cursor: "", direction: "forward" });
const [users, setUsers] = useState<User[]>([]);
// Fetch and save the users.
// This follows useSWR's recommendation for simple pagination:
// https://swr.vercel.app/docs/pagination#when-to-use-useswr
useSWR(`/api/admin/users?pageIndex=${pageIndex}`, get, {
onSuccess: setUsers,
useSWR(`/api/admin/users?direction=${pagination.direction}&cursor=${pagination.cursor}`, get, {
onSuccess: (data) => {
// When no more users can be found, trigger a toast to indicate why no
// changes have taken place. We have to maintain a non-empty set of
// users otherwise we can't paginate using a cursor (since we've lost the
// cursor).
if (data.length === 0) {
toast({
title: "No more users",
status: "warning",
duration: 1000,
isClosable: true,
});
return;
}
setUsers(data);
},
});
const toPreviousPage = () => {
setPageIndex(Math.max(0, pageIndex - 1));
setPagination({
cursor: users[0].display_name,
direction: "back",
});
};
const toNextPage = () => {
setPageIndex(pageIndex + 1);
setPagination({
cursor: users[users.length - 1].display_name,
direction: "forward",
});
};
// Present users in a naive table.
+20 -3
View File
@@ -157,10 +157,27 @@ export class OasstApiClient {
}
/**
* Returns the `max_count` `BackendUser`s stored by the backend.
* Returns the set of `BackendUser`s stored by the backend.
*
* @param {number} max_count - The maximum number of users to fetch.
* @param {string} cursor - The user's `display_name` to use when paginating.
* @param {boolean} isForward - If true and `cursor` is not empty, pages
* forward. If false and `cursor` is not empty, pages backwards.
* @returns {Promise<BackendUser[]>} A Promise that returns an array of `BackendUser` objects.
*/
async fetch_users(max_count: number): Promise<BackendUser[]> {
return this.get(`/api/v1/frontend_users/?max_count=${max_count}`);
async fetch_users(max_count: number, cursor: string, isForward: boolean): Promise<BackendUser[]> {
const params = new URLSearchParams();
params.append("max_count", max_count.toString());
// The backend API uses different query paramters depending on the
// pagination direction but they both take the same cursor value.
// Depending on direction, pick the right query param.
if (cursor !== "") {
params.append(isForward ? "gt" : "lt", cursor);
}
const BASE_URL = `/api/v1/frontend_users`;
const url = `${BASE_URL}/?${params.toString()}`;
return this.get(url);
}
/**
+13 -3
View File
@@ -2,17 +2,27 @@ import { withRole } from "src/lib/auth";
import { oasstApiClient } from "src/lib/oasst_api_client";
import prisma from "src/lib/prismadb";
/**
* The number of users to fetch in a single request. Could later be a query parameter.
*/
const PAGE_SIZE = 20;
/**
* Returns a list of user results from the database when the requesting user is
* a logged in admin.
*
* This takes two query params:
* - `cursor`: A string representing a user's `display_name`.
* - `direction`: Either "forward" or "backward" representing the pagination
* direction.
*/
const handler = withRole("admin", async (req, res) => {
// TODO(#673): Update this to support pagination.
const { cursor, direction } = req.query;
// First, get all the users according to the backend.
const all_users = await oasstApiClient.fetch_users(20);
const all_users = await oasstApiClient.fetch_users(PAGE_SIZE, cursor as string, direction === "forward");
// Next, get all the users stored in the web's auth datbase to fetch their role.
// Next, get all the users stored in the web's auth database to fetch their role.
const local_user_ids = all_users.map(({ id }) => id);
const local_users = await prisma.user.findMany({
where: {