fix(admin): user count is out of sync and search results are not cached in account management (#11980)

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **New Features**
  - Improved user management table with dynamic row count updates and enhanced synchronization of memoized user lists.
- **Bug Fixes**
  - User count and displayed data now update immediately after user creation, deletion, or import, ensuring accurate and consistent information.
- **Chores**
  - Enhanced internal state management for better responsiveness and reliability in the accounts section.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
JimmFly
2025-05-22 07:29:08 +00:00
parent 2999497f16
commit 940ab69374
9 changed files with 42 additions and 40 deletions

View File

@@ -1,3 +0,0 @@
query getUsersCount {
usersCount
}

View File

@@ -9,4 +9,5 @@ query listUsers($filter: ListUserInput!) {
emailVerified emailVerified
avatarUrl avatarUrl
} }
usersCount
} }

View File

@@ -171,14 +171,6 @@ export const getUserByEmailQuery = {
}`, }`,
}; };
export const getUsersCountQuery = {
id: 'getUsersCountQuery' as const,
op: 'getUsersCount',
query: `query getUsersCount {
usersCount
}`,
};
export const importUsersMutation = { export const importUsersMutation = {
id: 'importUsersMutation' as const, id: 'importUsersMutation' as const,
op: 'ImportUsers', op: 'ImportUsers',
@@ -212,6 +204,7 @@ export const listUsersQuery = {
emailVerified emailVerified
avatarUrl avatarUrl
} }
usersCount
}`, }`,
}; };

View File

@@ -2811,10 +2811,6 @@ export type GetUserByEmailQuery = {
} | null; } | null;
}; };
export type GetUsersCountQueryVariables = Exact<{ [key: string]: never }>;
export type GetUsersCountQuery = { __typename?: 'Query'; usersCount: number };
export type ImportUsersMutationVariables = Exact<{ export type ImportUsersMutationVariables = Exact<{
input: ImportUsersInput; input: ImportUsersInput;
}>; }>;
@@ -2833,6 +2829,7 @@ export type ListUsersQueryVariables = Exact<{
export type ListUsersQuery = { export type ListUsersQuery = {
__typename?: 'Query'; __typename?: 'Query';
usersCount: number;
users: Array<{ users: Array<{
__typename?: 'UserType'; __typename?: 'UserType';
id: string; id: string;
@@ -4969,11 +4966,6 @@ export type Queries =
variables: GetUserByEmailQueryVariables; variables: GetUserByEmailQueryVariables;
response: GetUserByEmailQuery; response: GetUserByEmailQuery;
} }
| {
name: 'getUsersCountQuery';
variables: GetUsersCountQueryVariables;
response: GetUsersCountQuery;
}
| { | {
name: 'listUsersQuery'; name: 'listUsersQuery';
variables: ListUsersQueryVariables; variables: ListUsersQueryVariables;

View File

@@ -4,8 +4,8 @@ import { useQuery } from '@affine/admin/use-query';
import { getUserByEmailQuery } from '@affine/graphql'; import { getUserByEmailQuery } from '@affine/graphql';
import { ExportIcon, ImportIcon, PlusIcon } from '@blocksuite/icons/rc'; import { ExportIcon, ImportIcon, PlusIcon } from '@blocksuite/icons/rc';
import type { Table } from '@tanstack/react-table'; import type { Table } from '@tanstack/react-table';
import type { Dispatch, SetStateAction } from 'react';
import { import {
type SetStateAction,
startTransition, startTransition,
useCallback, useCallback,
useEffect, useEffect,
@@ -22,8 +22,11 @@ import { CreateUserForm } from './user-form';
interface DataTableToolbarProps<TData> { interface DataTableToolbarProps<TData> {
data: TData[]; data: TData[];
setDataTable: (data: TData[]) => void; usersCount: number;
selectedUsers: UserType[]; selectedUsers: UserType[];
setDataTable: (data: TData[]) => void;
setRowCount: (rowCount: number) => void;
setMemoUsers: Dispatch<SetStateAction<UserType[]>>;
table?: Table<TData>; table?: Table<TData>;
} }
@@ -60,8 +63,11 @@ function useDebouncedValue<T>(value: T, delay: number): T {
export function DataTableToolbar<TData>({ export function DataTableToolbar<TData>({
data, data,
usersCount,
selectedUsers, selectedUsers,
setDataTable, setDataTable,
setRowCount,
setMemoUsers,
table, table,
}: DataTableToolbarProps<TData>) { }: DataTableToolbarProps<TData>) {
const [value, setValue] = useState(''); const [value, setValue] = useState('');
@@ -90,13 +96,25 @@ export function DataTableToolbar<TData>({
startTransition(() => { startTransition(() => {
if (!debouncedValue) { if (!debouncedValue) {
setDataTable(data); setDataTable(data);
setRowCount(usersCount);
} else if (result) { } else if (result) {
setMemoUsers(prev => [...new Set([...prev, result])]);
setDataTable([result as TData]); setDataTable([result as TData]);
setRowCount(1);
} else { } else {
setDataTable([]); setDataTable([]);
setRowCount(0);
} }
}); });
}, [data, debouncedValue, result, setDataTable]); }, [
data,
debouncedValue,
result,
setDataTable,
setMemoUsers,
setRowCount,
usersCount,
]);
const onValueChange = useCallback( const onValueChange = useCallback(
(e: { currentTarget: { value: SetStateAction<string> } }) => { (e: { currentTarget: { value: SetStateAction<string> } }) => {

View File

@@ -21,13 +21,14 @@ import { type Dispatch, type SetStateAction, useEffect, useState } from 'react';
import type { UserType } from '../schema'; import type { UserType } from '../schema';
import { DataTablePagination } from './data-table-pagination'; import { DataTablePagination } from './data-table-pagination';
import { DataTableToolbar } from './data-table-toolbar'; import { DataTableToolbar } from './data-table-toolbar';
import { useUserCount } from './use-user-management';
interface DataTableProps<TData, TValue> { interface DataTableProps<TData, TValue> {
columns: ColumnDef<TData, TValue>[]; columns: ColumnDef<TData, TValue>[];
data: TData[]; data: TData[];
pagination: PaginationState; pagination: PaginationState;
usersCount: number;
selectedUsers: UserType[]; selectedUsers: UserType[];
setMemoUsers: Dispatch<SetStateAction<UserType[]>>;
onPaginationChange: Dispatch< onPaginationChange: Dispatch<
SetStateAction<{ SetStateAction<{
pageIndex: number; pageIndex: number;
@@ -40,22 +41,23 @@ export function DataTable<TData extends { id: string }, TValue>({
columns, columns,
data, data,
pagination, pagination,
usersCount,
selectedUsers, selectedUsers,
setMemoUsers,
onPaginationChange, onPaginationChange,
}: DataTableProps<TData, TValue>) { }: DataTableProps<TData, TValue>) {
const usersCount = useUserCount();
const [rowSelection, setRowSelection] = useState({}); const [rowSelection, setRowSelection] = useState({});
const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]); const [columnFilters, setColumnFilters] = useState<ColumnFiltersState>([]);
const [tableData, setTableData] = useState(data); const [tableData, setTableData] = useState(data);
const [rowCount, setRowCount] = useState(usersCount);
const table = useReactTable({ const table = useReactTable({
data: tableData, data: tableData,
columns, columns,
getCoreRowModel: getCoreRowModel(), getCoreRowModel: getCoreRowModel(),
getRowId: row => row.id, getRowId: row => row.id,
manualPagination: true, manualPagination: true,
rowCount: usersCount, rowCount: rowCount,
enableFilters: true, enableFilters: true,
onPaginationChange: onPaginationChange, onPaginationChange: onPaginationChange,
enableRowSelection: true, enableRowSelection: true,
@@ -72,13 +74,20 @@ export function DataTable<TData extends { id: string }, TValue>({
setTableData(data); setTableData(data);
}, [data]); }, [data]);
useEffect(() => {
setRowCount(usersCount);
}, [usersCount]);
return ( return (
<div className="flex flex-col gap-4 py-5 px-6 h-full overflow-auto"> <div className="flex flex-col gap-4 py-5 px-6 h-full overflow-auto">
<DataTableToolbar <DataTableToolbar
setDataTable={setTableData} setDataTable={setTableData}
data={data} data={data}
usersCount={usersCount}
table={table} table={table}
selectedUsers={selectedUsers} selectedUsers={selectedUsers}
setRowCount={setRowCount}
setMemoUsers={setMemoUsers}
/> />
<div className="rounded-md border h-full flex flex-col overflow-auto"> <div className="rounded-md border h-full flex flex-col overflow-auto">
<Table> <Table>

View File

@@ -2,7 +2,6 @@ import {
useMutateQueryResource, useMutateQueryResource,
useMutation, useMutation,
} from '@affine/admin/use-mutation'; } from '@affine/admin/use-mutation';
import { useQuery } from '@affine/admin/use-query';
import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks'; import { useAsyncCallback } from '@affine/core/components/hooks/affine-async-hooks';
import { import {
createChangePasswordUrlMutation, createChangePasswordUrlMutation,
@@ -10,7 +9,6 @@ import {
deleteUserMutation, deleteUserMutation,
disableUserMutation, disableUserMutation,
enableUserMutation, enableUserMutation,
getUsersCountQuery,
type ImportUsersInput, type ImportUsersInput,
type ImportUsersMutation, type ImportUsersMutation,
importUsersMutation, importUsersMutation,
@@ -225,15 +223,6 @@ export const useDisableUser = () => {
return disableById; return disableById;
}; };
export const useUserCount = () => {
const {
data: { usersCount },
} = useQuery({
query: getUsersCountQuery,
});
return usersCount;
};
export const useImportUsers = () => { export const useImportUsers = () => {
const { trigger: importUsers } = useMutation({ const { trigger: importUsers } = useMutation({
mutation: importUsersMutation, mutation: importUsersMutation,

View File

@@ -7,7 +7,7 @@ import type { UserType } from './schema';
import { useUserList } from './use-user-list'; import { useUserList } from './use-user-list';
export function AccountPage() { export function AccountPage() {
const { users, pagination, setPagination } = useUserList(); const { users, pagination, setPagination, usersCount } = useUserList();
// Remember the user temporarily, because userList is paginated on the server side,can't get all users at once. // Remember the user temporarily, because userList is paginated on the server side,can't get all users at once.
const [memoUsers, setMemoUsers] = useState<UserType[]>([]); const [memoUsers, setMemoUsers] = useState<UserType[]>([]);
@@ -32,8 +32,10 @@ export function AccountPage() {
data={users} data={users}
columns={columns} columns={columns}
pagination={pagination} pagination={pagination}
usersCount={usersCount}
onPaginationChange={setPagination} onPaginationChange={setPagination}
selectedUsers={selectedUsers} selectedUsers={selectedUsers}
setMemoUsers={setMemoUsers}
/> />
</div> </div>
); );

View File

@@ -8,7 +8,7 @@ export const useUserList = () => {
pageSize: 10, pageSize: 10,
}); });
const { const {
data: { users }, data: { users, usersCount },
} = useQuery({ } = useQuery({
query: listUsersQuery, query: listUsersQuery,
variables: { variables: {
@@ -23,5 +23,6 @@ export const useUserList = () => {
users, users,
pagination, pagination,
setPagination, setPagination,
usersCount,
}; };
}; };