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:
@@ -1,3 +0,0 @@
|
|||||||
query getUsersCount {
|
|
||||||
usersCount
|
|
||||||
}
|
|
||||||
@@ -9,4 +9,5 @@ query listUsers($filter: ListUserInput!) {
|
|||||||
emailVerified
|
emailVerified
|
||||||
avatarUrl
|
avatarUrl
|
||||||
}
|
}
|
||||||
|
usersCount
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
}`,
|
}`,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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> } }) => {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user