From ae2a5ac20ab0f3a9c468a57c888f395b7a49d7f7 Mon Sep 17 00:00:00 2001 From: Leonardo Ostjen Couto Date: Tue, 14 Sep 2021 11:34:17 -0300 Subject: [PATCH] [FIX] User list not being updated after creation/deletion of user (#23032) * created handleDeletedUser callback * removed toastr error * migrated endpoint call from UsersTable to UsersPage * reload component after user delete * replicated behavior for add users + spreaded props * lint * small refact * + lint fixes * removed reloadTable from editUser * renamed tablereload to reloadTable * destructured props on function parameters * moved useQuery to parent * Move hook to module scope Co-authored-by: Tasso Evangelista --- client/views/admin/users/AddUser.js | 3 +- client/views/admin/users/EditUser.js | 3 +- client/views/admin/users/UserInfo.js | 4 +- client/views/admin/users/UserInfoActions.js | 10 +++- client/views/admin/users/UsersPage.js | 53 +++++++++++++++++++-- client/views/admin/users/UsersTable.js | 51 ++------------------ 6 files changed, 69 insertions(+), 55 deletions(-) diff --git a/client/views/admin/users/AddUser.js b/client/views/admin/users/AddUser.js index e7e1ff2a1fa..72578264d18 100644 --- a/client/views/admin/users/AddUser.js +++ b/client/views/admin/users/AddUser.js @@ -9,7 +9,7 @@ import { useEndpointData } from '../../../hooks/useEndpointData'; import { useForm } from '../../../hooks/useForm'; import UserForm from './UserForm'; -export function AddUser({ roles, ...props }) { +export function AddUser({ roles, reloadTable, ...props }) { const t = useTranslation(); const router = useRoute('admin-users'); @@ -93,6 +93,7 @@ export function AddUser({ roles, ...props }) { const result = await saveAction(); if (result.success) { goToUser(result.user._id); + reloadTable(); } }); diff --git a/client/views/admin/users/EditUser.js b/client/views/admin/users/EditUser.js index c6f27b5a366..9e58c96c514 100644 --- a/client/views/admin/users/EditUser.js +++ b/client/views/admin/users/EditUser.js @@ -26,7 +26,7 @@ const getInitialValue = (data) => ({ statusText: data.statusText ?? '', }); -function EditUser({ data, roles, ...props }) { +function EditUser({ data, roles, reloadTable, ...props }) { const t = useTranslation(); const [avatarObj, setAvatarObj] = useState(); @@ -143,6 +143,7 @@ function EditUser({ data, roles, ...props }) { if (avatarObj) { await updateAvatar(); } + reloadTable(); goToUser(data._id); } }, [avatarObj, data._id, goToUser, saveAction, updateAvatar, values, errors, validationKeys]); diff --git a/client/views/admin/users/UserInfo.js b/client/views/admin/users/UserInfo.js index 319c1386897..63d94b1b4ce 100644 --- a/client/views/admin/users/UserInfo.js +++ b/client/views/admin/users/UserInfo.js @@ -14,7 +14,7 @@ import { getUserEmailVerified } from '../../../lib/getUserEmailVerified'; import UserInfo from '../../room/contextualBar/UserInfo/UserInfo'; import { UserInfoActions } from './UserInfoActions'; -export function UserInfoWithData({ uid, username, ...props }) { +export function UserInfoWithData({ uid, username, reloadTable, ...props }) { const t = useTranslation(); const showRealNames = useSetting('UI_Use_Real_Name'); const approveManuallyUsers = useSetting('Accounts_ManuallyApproveNewUsers'); @@ -33,6 +33,7 @@ export function UserInfoWithData({ uid, username, ...props }) { ); const onChange = useMutableCallback(() => reload()); + const onDelete = useMutableCallback(() => (reloadTable ? reloadTable() : null)); const user = useMemo(() => { const { user } = data || { user: {} }; @@ -99,6 +100,7 @@ export function UserInfoWithData({ uid, username, ...props }) { _id={data.user._id} username={data.user.username} onChange={onChange} + onDelete={onDelete} /> ) } diff --git a/client/views/admin/users/UserInfoActions.js b/client/views/admin/users/UserInfoActions.js index 263a00e96b6..ebc4fd2ffc4 100644 --- a/client/views/admin/users/UserInfoActions.js +++ b/client/views/admin/users/UserInfoActions.js @@ -13,7 +13,7 @@ import { useTranslation } from '../../../contexts/TranslationContext'; import { useActionSpread } from '../../hooks/useActionSpread'; import UserInfo from '../../room/contextualBar/UserInfo'; -export const UserInfoActions = ({ username, _id, isActive, isAdmin, onChange }) => { +export const UserInfoActions = ({ username, _id, isActive, isAdmin, onChange, onDelete }) => { const t = useTranslation(); const setModal = useSetModal(); @@ -36,6 +36,12 @@ export const UserInfoActions = ({ username, _id, isActive, isAdmin, onChange }) onChange(); }, [setModal, onChange]); + const handleDeletedUser = () => { + setModal(); + userRoute.push({}); + onDelete(); + }; + const confirmOwnerChanges = (action, modalProps = {}) => async () => { @@ -79,7 +85,7 @@ export const UserInfoActions = ({ username, _id, isActive, isAdmin, onChange }) const result = await deleteUserEndpoint(deleteUserQuery); if (result.success) { setModal( - + {t('User_has_been_deleted')} , ); diff --git a/client/views/admin/users/UsersPage.js b/client/views/admin/users/UsersPage.js index 90c0d1c5170..e436d1daec4 100644 --- a/client/views/admin/users/UsersPage.js +++ b/client/views/admin/users/UsersPage.js @@ -1,16 +1,51 @@ import { Button, ButtonGroup, Icon } from '@rocket.chat/fuselage'; -import React from 'react'; +import { useDebouncedValue } from '@rocket.chat/fuselage-hooks'; +import React, { useMemo, useState } from 'react'; import Page from '../../../components/Page'; import VerticalBar from '../../../components/VerticalBar'; import { useRoute, useCurrentRoute } from '../../../contexts/RouterContext'; import { useTranslation } from '../../../contexts/TranslationContext'; +import { useEndpointData } from '../../../hooks/useEndpointData'; import { AddUser } from './AddUser'; import EditUserWithData from './EditUserWithData'; import { InviteUsers } from './InviteUsers'; import { UserInfoWithData } from './UserInfo'; import UsersTable from './UsersTable'; +const sortDir = (sortDir) => (sortDir === 'asc' ? 1 : -1); + +const useQuery = ({ text, itemsPerPage, current }, sortFields) => + useMemo( + () => ({ + fields: JSON.stringify({ + name: 1, + username: 1, + emails: 1, + roles: 1, + status: 1, + avatarETag: 1, + active: 1, + }), + query: JSON.stringify({ + $or: [ + { 'emails.address': { $regex: text || '', $options: 'i' } }, + { username: { $regex: text || '', $options: 'i' } }, + { name: { $regex: text || '', $options: 'i' } }, + ], + }), + sort: JSON.stringify( + sortFields.reduce((agg, [column, direction]) => { + agg[column] = sortDir(direction); + return agg; + }, {}), + ), + ...(itemsPerPage && { count: itemsPerPage }), + ...(current && { offset: current }), + }), + [text, itemsPerPage, current, sortFields], + ); + function UsersPage() { const t = useTranslation(); @@ -28,6 +63,16 @@ function UsersPage() { usersRoute.push({ context: 'invite' }); }; + const [params] = useState({ text: '', current: 0, itemsPerPage: 25 }); + const [sort] = useState([ + ['name', 'asc'], + ['usernames', 'asc'], + ]); + + const debouncedParams = useDebouncedValue(params, 500); + const debouncedSort = useDebouncedValue(sort, 500); + const query = useQuery(debouncedParams, debouncedSort); + const { value: data = {}, reload } = useEndpointData('users.list', query); const [, { context, id }] = useCurrentRoute(); return ( @@ -44,7 +89,7 @@ function UsersPage() { - + {context && ( @@ -57,9 +102,9 @@ function UsersPage() { - {context === 'info' && } + {context === 'info' && } {context === 'edit' && } - {context === 'new' && } + {context === 'new' && } {context === 'invite' && } )} diff --git a/client/views/admin/users/UsersTable.js b/client/views/admin/users/UsersTable.js index af96033e811..60031db4ca6 100644 --- a/client/views/admin/users/UsersTable.js +++ b/client/views/admin/users/UsersTable.js @@ -1,61 +1,20 @@ -import { useDebouncedValue, useMediaQuery } from '@rocket.chat/fuselage-hooks'; -import React, { useMemo, useCallback, useState } from 'react'; +import { useMediaQuery } from '@rocket.chat/fuselage-hooks'; +import React, { useCallback, useState } from 'react'; import FilterByText from '../../../components/FilterByText'; import GenericTable from '../../../components/GenericTable'; import { useRoute } from '../../../contexts/RouterContext'; import { useTranslation } from '../../../contexts/TranslationContext'; -import { useEndpointData } from '../../../hooks/useEndpointData'; import UserRow from './UserRow'; -const sortDir = (sortDir) => (sortDir === 'asc' ? 1 : -1); - -const useQuery = ({ text, itemsPerPage, current }, sortFields) => - useMemo( - () => ({ - fields: JSON.stringify({ - name: 1, - username: 1, - emails: 1, - roles: 1, - status: 1, - avatarETag: 1, - active: 1, - }), - query: JSON.stringify({ - $or: [ - { 'emails.address': { $regex: text || '', $options: 'i' } }, - { username: { $regex: text || '', $options: 'i' } }, - { name: { $regex: text || '', $options: 'i' } }, - ], - }), - sort: JSON.stringify( - sortFields.reduce((agg, [column, direction]) => { - agg[column] = sortDir(direction); - return agg; - }, {}), - ), - ...(itemsPerPage && { count: itemsPerPage }), - ...(current && { offset: current }), - }), - [text, itemsPerPage, current, sortFields], - ); - -function UsersTable() { +function UsersTable(props) { const t = useTranslation(); - const [params, setParams] = useState({ text: '', current: 0, itemsPerPage: 25 }); const [sort, setSort] = useState([ ['name', 'asc'], ['usernames', 'asc'], ]); - const debouncedParams = useDebouncedValue(params, 500); - const debouncedSort = useDebouncedValue(sort, 500); - const query = useQuery(debouncedParams, debouncedSort); - - const { value: data = {} } = useEndpointData('users.list', query); - const usersRoute = useRoute('admin-users'); const onClick = useCallback( @@ -159,8 +118,8 @@ function UsersTable() { } - results={data.users} - total={data.total} + results={props.users} + total={props.total} setParams={setParams} params={params} renderFilter={({ onChange, ...props }) => (