mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Admin: Use backend sort (#75228)
* Admin: Use InteractiveTable * Admin: Fix pagination * Admin: Use CellWrapper * Admin: Split components * Admin: Separate OrgUsersTable * Admin: Remove UsersTable * Admin: Use the new table for TeamList * Admin: Cleanup TeamList page * Admin: Add edit team action * Admin: Use explicit edit action instead of a link wrapper * Admin: Fix responsive styles * Cleanup * Remove redundant sort * Add item key * Fix icon styles * Set loading by default * Use separate pagination component * Use default sorting functionality * Fix merge conflicts * Update betterer * Add controlled sort * Revert pagination changes * Move pagination inside OrgUsersTable.tsx * Add missing prop * Sort users table * Fix tests * Remove loader * Add sort to Teams * Cleanup * Update loadTeams action * Remove sort condition
This commit is contained in:
parent
5796836662
commit
4a692fc82e
@ -1,5 +1,5 @@
|
||||
// BETTERER RESULTS V2.
|
||||
//
|
||||
//
|
||||
// If this file contains merge conflicts, use `betterer merge` to automatically resolve them:
|
||||
// https://phenomnomnominal.github.io/betterer/docs/results-file/#merge
|
||||
//
|
||||
|
@ -8,11 +8,10 @@ import { LinkButton, RadioButtonGroup, useStyles2, FilterInput } from '@grafana/
|
||||
import { Page } from 'app/core/components/Page/Page';
|
||||
import { contextSrv } from 'app/core/core';
|
||||
|
||||
import PageLoader from '../../core/components/PageLoader/PageLoader';
|
||||
import { AccessControlAction, StoreState, UserFilter } from '../../types';
|
||||
|
||||
import { UsersTable } from './Users/UsersTable';
|
||||
import { changeFilter, changePage, changeQuery, fetchUsers } from './state/actions';
|
||||
import { changeFilter, changePage, changeQuery, changeSort, fetchUsers } from './state/actions';
|
||||
|
||||
export interface FilterProps {
|
||||
filters: UserFilter[];
|
||||
@ -31,6 +30,7 @@ const mapDispatchToProps = {
|
||||
changeQuery,
|
||||
changePage,
|
||||
changeFilter,
|
||||
changeSort,
|
||||
};
|
||||
|
||||
const mapStateToProps = (state: StoreState) => ({
|
||||
@ -40,7 +40,6 @@ const mapStateToProps = (state: StoreState) => ({
|
||||
totalPages: state.userListAdmin.totalPages,
|
||||
page: state.userListAdmin.page,
|
||||
filters: state.userListAdmin.filters,
|
||||
isLoading: state.userListAdmin.isLoading,
|
||||
});
|
||||
|
||||
const connector = connect(mapStateToProps, mapDispatchToProps);
|
||||
@ -57,10 +56,10 @@ const UserListAdminPageUnConnected = ({
|
||||
showPaging,
|
||||
changeFilter,
|
||||
filters,
|
||||
isLoading,
|
||||
totalPages,
|
||||
page,
|
||||
changePage,
|
||||
changeSort,
|
||||
}: Props) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
@ -97,17 +96,14 @@ const UserListAdminPageUnConnected = ({
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{isLoading ? (
|
||||
<PageLoader />
|
||||
) : (
|
||||
<UsersTable
|
||||
users={users}
|
||||
showPaging={showPaging}
|
||||
totalPages={totalPages}
|
||||
onChangePage={changePage}
|
||||
currentPage={page}
|
||||
/>
|
||||
)}
|
||||
<UsersTable
|
||||
users={users}
|
||||
showPaging={showPaging}
|
||||
totalPages={totalPages}
|
||||
onChangePage={changePage}
|
||||
currentPage={page}
|
||||
fetchData={changeSort}
|
||||
/>
|
||||
</Page.Contents>
|
||||
);
|
||||
};
|
||||
|
@ -13,6 +13,7 @@ import {
|
||||
Tag,
|
||||
InteractiveTable,
|
||||
Column,
|
||||
FetchDataFunc,
|
||||
Pagination,
|
||||
HorizontalGroup,
|
||||
VerticalGroup,
|
||||
@ -53,16 +54,24 @@ export interface Props {
|
||||
orgId?: number;
|
||||
onRoleChange: (role: OrgRole, user: OrgUser) => void;
|
||||
onRemoveUser: (user: OrgUser) => void;
|
||||
fetchData?: FetchDataFunc<OrgUser>;
|
||||
changePage: (page: number) => void;
|
||||
page: number;
|
||||
totalPages: number;
|
||||
}
|
||||
|
||||
export const OrgUsersTable = ({ users, orgId, onRoleChange, onRemoveUser, changePage, page, totalPages }: Props) => {
|
||||
export const OrgUsersTable = ({
|
||||
users,
|
||||
orgId,
|
||||
onRoleChange,
|
||||
onRemoveUser,
|
||||
fetchData,
|
||||
changePage,
|
||||
page,
|
||||
totalPages,
|
||||
}: Props) => {
|
||||
const [userToRemove, setUserToRemove] = useState<OrgUser | null>(null);
|
||||
const [roleOptions, setRoleOptions] = useState<Role[]>([]);
|
||||
const enableSort = totalPages === 1;
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
useEffect(() => {
|
||||
async function fetchOptions() {
|
||||
@ -91,27 +100,25 @@ export const OrgUsersTable = ({ users, orgId, onRoleChange, onRemoveUser, change
|
||||
id: 'login',
|
||||
header: 'Login',
|
||||
cell: ({ cell: { value } }: Cell<'login'>) => <div>{value}</div>,
|
||||
sortType: enableSort ? 'string' : undefined,
|
||||
sortType: 'string',
|
||||
},
|
||||
{
|
||||
id: 'email',
|
||||
header: 'Email',
|
||||
cell: ({ cell: { value } }: Cell<'email'>) => value,
|
||||
sortType: enableSort ? 'string' : undefined,
|
||||
sortType: 'string',
|
||||
},
|
||||
{
|
||||
id: 'name',
|
||||
header: 'Name',
|
||||
cell: ({ cell: { value } }: Cell<'name'>) => value,
|
||||
sortType: enableSort ? 'string' : undefined,
|
||||
sortType: 'string',
|
||||
},
|
||||
{
|
||||
id: 'lastSeenAtAge',
|
||||
header: 'Last active',
|
||||
cell: ({ cell: { value } }: Cell<'lastSeenAtAge'>) => value,
|
||||
sortType: enableSort
|
||||
? (a, b) => new Date(a.original.lastSeenAt).getTime() - new Date(b.original.lastSeenAt).getTime()
|
||||
: undefined,
|
||||
sortType: (a, b) => new Date(a.original.lastSeenAt).getTime() - new Date(b.original.lastSeenAt).getTime(),
|
||||
},
|
||||
{
|
||||
id: 'role',
|
||||
@ -175,17 +182,15 @@ export const OrgUsersTable = ({ users, orgId, onRoleChange, onRemoveUser, change
|
||||
},
|
||||
},
|
||||
],
|
||||
[orgId, roleOptions, onRoleChange, enableSort]
|
||||
[orgId, roleOptions, onRoleChange]
|
||||
);
|
||||
|
||||
return (
|
||||
<VerticalGroup spacing="md" data-testid={selectors.container}>
|
||||
<div className={styles.wrapper}>
|
||||
<InteractiveTable columns={columns} data={users} getRowId={(user) => String(user.userId)} />
|
||||
<HorizontalGroup justify="flex-end" height={'auto'}>
|
||||
<Pagination onNavigate={changePage} currentPage={page} numberOfPages={totalPages} hideWhenSinglePage={true} />
|
||||
</HorizontalGroup>
|
||||
</div>
|
||||
<InteractiveTable columns={columns} data={users} getRowId={(user) => String(user.userId)} fetchData={fetchData} />
|
||||
<HorizontalGroup justify="flex-end">
|
||||
<Pagination onNavigate={changePage} currentPage={page} numberOfPages={totalPages} hideWhenSinglePage={true} />
|
||||
</HorizontalGroup>
|
||||
{Boolean(userToRemove) && (
|
||||
<ConfirmModal
|
||||
body={`Are you sure you want to delete user ${userToRemove?.login}?`}
|
||||
|
@ -13,6 +13,7 @@ import {
|
||||
Column,
|
||||
VerticalGroup,
|
||||
HorizontalGroup,
|
||||
FetchDataFunc,
|
||||
} from '@grafana/ui';
|
||||
import { TagBadge } from 'app/core/components/TagFilter/TagBadge';
|
||||
import { UserDTO } from 'app/types';
|
||||
@ -28,11 +29,18 @@ interface UsersTableProps {
|
||||
totalPages: number;
|
||||
onChangePage: (page: number) => void;
|
||||
currentPage: number;
|
||||
fetchData?: FetchDataFunc<UserDTO>;
|
||||
}
|
||||
|
||||
export const UsersTable = ({ users, showPaging, totalPages, onChangePage, currentPage }: UsersTableProps) => {
|
||||
export const UsersTable = ({
|
||||
users,
|
||||
showPaging,
|
||||
totalPages,
|
||||
onChangePage,
|
||||
currentPage,
|
||||
fetchData,
|
||||
}: UsersTableProps) => {
|
||||
const showLicensedRole = useMemo(() => users.some((user) => user.licensedRole), [users]);
|
||||
const enableSort = totalPages === 1;
|
||||
const columns: Array<Column<UserDTO>> = useMemo(
|
||||
() => [
|
||||
{
|
||||
@ -44,25 +52,25 @@ export const UsersTable = ({ users, showPaging, totalPages, onChangePage, curren
|
||||
id: 'login',
|
||||
header: 'Login',
|
||||
cell: ({ cell: { value } }: Cell<'login'>) => value,
|
||||
sortType: enableSort ? 'string' : undefined,
|
||||
sortType: 'string',
|
||||
},
|
||||
{
|
||||
id: 'email',
|
||||
header: 'Email',
|
||||
cell: ({ cell: { value } }: Cell<'email'>) => value,
|
||||
sortType: enableSort ? 'string' : undefined,
|
||||
sortType: 'string',
|
||||
},
|
||||
{
|
||||
id: 'name',
|
||||
header: 'Name',
|
||||
cell: ({ cell: { value } }: Cell<'name'>) => value,
|
||||
sortType: enableSort ? 'string' : undefined,
|
||||
sortType: 'string',
|
||||
},
|
||||
{
|
||||
id: 'orgs',
|
||||
header: 'Belongs to',
|
||||
cell: OrgUnitsCell,
|
||||
sortType: enableSort ? (a, b) => (a.original.orgs?.length || 0) - (b.original.orgs?.length || 0) : undefined,
|
||||
sortType: (a, b) => (a.original.orgs?.length || 0) - (b.original.orgs?.length || 0),
|
||||
},
|
||||
...(showLicensedRole
|
||||
? [
|
||||
@ -71,7 +79,7 @@ export const UsersTable = ({ users, showPaging, totalPages, onChangePage, curren
|
||||
header: 'Licensed role',
|
||||
cell: LicensedRoleCell,
|
||||
// Needs the assertion here, the types are not inferred correctly due to the conditional assignment
|
||||
sortType: enableSort ? ('string' as const) : undefined,
|
||||
sortType: 'string' as const,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
@ -83,9 +91,7 @@ export const UsersTable = ({ users, showPaging, totalPages, onChangePage, curren
|
||||
iconName: 'question-circle',
|
||||
},
|
||||
cell: LastSeenAtCell,
|
||||
sortType: enableSort
|
||||
? (a, b) => new Date(a.original.lastSeenAt!).getTime() - new Date(b.original.lastSeenAt!).getTime()
|
||||
: undefined,
|
||||
sortType: (a, b) => new Date(a.original.lastSeenAt!).getTime() - new Date(b.original.lastSeenAt!).getTime(),
|
||||
},
|
||||
{
|
||||
id: 'authLabels',
|
||||
@ -113,11 +119,11 @@ export const UsersTable = ({ users, showPaging, totalPages, onChangePage, curren
|
||||
},
|
||||
},
|
||||
],
|
||||
[showLicensedRole, enableSort]
|
||||
[showLicensedRole]
|
||||
);
|
||||
return (
|
||||
<VerticalGroup spacing={'md'}>
|
||||
<InteractiveTable columns={columns} data={users} getRowId={(user) => String(user.id)} />
|
||||
<InteractiveTable columns={columns} data={users} getRowId={(user) => String(user.id)} fetchData={fetchData} />
|
||||
{showPaging && (
|
||||
<HorizontalGroup justify={'flex-end'}>
|
||||
<Pagination numberOfPages={totalPages} currentPage={currentPage} onNavigate={onChangePage} />
|
||||
|
@ -2,6 +2,7 @@ import { debounce } from 'lodash';
|
||||
|
||||
import { dateTimeFormatTimeAgo } from '@grafana/data';
|
||||
import { featureEnabled, getBackendSrv, isFetchError, locationService } from '@grafana/runtime';
|
||||
import { FetchDataArgs } from '@grafana/ui';
|
||||
import config from 'app/core/config';
|
||||
import { contextSrv } from 'app/core/core';
|
||||
import { accessControlQueryParam } from 'app/core/utils/accessControl';
|
||||
@ -26,6 +27,7 @@ import {
|
||||
filterChanged,
|
||||
usersFetchBegin,
|
||||
usersFetchEnd,
|
||||
sortChanged,
|
||||
} from './reducers';
|
||||
// UserAdminPage
|
||||
|
||||
@ -281,10 +283,12 @@ const getFilters = (filters: UserFilter[]) => {
|
||||
export function fetchUsers(): ThunkResult<void> {
|
||||
return async (dispatch, getState) => {
|
||||
try {
|
||||
const { perPage, page, query, filters } = getState().userListAdmin;
|
||||
const result = await getBackendSrv().get(
|
||||
`/api/users/search?perpage=${perPage}&page=${page}&query=${query}&${getFilters(filters)}`
|
||||
);
|
||||
const { perPage, page, query, filters, sort } = getState().userListAdmin;
|
||||
let url = `/api/users/search?perpage=${perPage}&page=${page}&query=${query}&${getFilters(filters)}`;
|
||||
if (sort) {
|
||||
url += `&sort=${sort}`;
|
||||
}
|
||||
const result = await getBackendSrv().get(url);
|
||||
dispatch(usersFetched(result));
|
||||
} catch (error) {
|
||||
usersFetchEnd();
|
||||
@ -318,3 +322,15 @@ export function changePage(page: number): ThunkResult<void> {
|
||||
dispatch(fetchUsers());
|
||||
};
|
||||
}
|
||||
|
||||
export function changeSort({ sortBy }: FetchDataArgs<UserDTO>): ThunkResult<void> {
|
||||
const sort = sortBy.length ? `${sortBy[0].id}-${sortBy[0].desc ? 'desc' : 'asc'}` : undefined;
|
||||
return async (dispatch, getState) => {
|
||||
const currentSort = getState().userListAdmin.sort;
|
||||
if (currentSort !== sort) {
|
||||
dispatch(usersFetchBegin());
|
||||
dispatch(sortChanged(sort));
|
||||
dispatch(fetchUsers());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -173,6 +173,11 @@ export const userListAdminSlice = createSlice({
|
||||
...state,
|
||||
page: action.payload,
|
||||
}),
|
||||
sortChanged: (state, action: PayloadAction<UserListAdminState['sort']>) => ({
|
||||
...state,
|
||||
page: 0,
|
||||
sort: action.payload,
|
||||
}),
|
||||
filterChanged: (state, action: PayloadAction<UserFilter>) => {
|
||||
const { name, value } = action.payload;
|
||||
|
||||
@ -192,7 +197,7 @@ export const userListAdminSlice = createSlice({
|
||||
},
|
||||
});
|
||||
|
||||
export const { usersFetched, usersFetchBegin, usersFetchEnd, queryChanged, pageChanged, filterChanged } =
|
||||
export const { usersFetched, usersFetchBegin, usersFetchEnd, queryChanged, pageChanged, filterChanged, sortChanged } =
|
||||
userListAdminSlice.actions;
|
||||
export const userListAdminReducer = userListAdminSlice.reducer;
|
||||
|
||||
|
@ -25,6 +25,7 @@ const setup = (propOverrides?: object) => {
|
||||
deleteTeam: jest.fn(),
|
||||
changePage: jest.fn(),
|
||||
changeQuery: jest.fn(),
|
||||
changeSort: jest.fn(),
|
||||
query: '',
|
||||
totalPages: 0,
|
||||
page: 0,
|
||||
|
@ -25,7 +25,7 @@ import { AccessControlAction, Role, StoreState, Team } from 'app/types';
|
||||
import { TeamRolePicker } from '../../core/components/RolePicker/TeamRolePicker';
|
||||
import { Avatar } from '../admin/Users/Avatar';
|
||||
|
||||
import { deleteTeam, loadTeams, changePage, changeQuery } from './state/actions';
|
||||
import { deleteTeam, loadTeams, changePage, changeQuery, changeSort } from './state/actions';
|
||||
import { isPermissionTeamAdmin } from './state/selectors';
|
||||
|
||||
type Cell<T extends keyof Team = keyof Team> = CellProps<Team, Team[T]>;
|
||||
@ -48,9 +48,9 @@ export const TeamList = ({
|
||||
editorsCanAdmin,
|
||||
page,
|
||||
changePage,
|
||||
changeSort,
|
||||
}: Props) => {
|
||||
const [roleOptions, setRoleOptions] = useState<Role[]>([]);
|
||||
const enableSort = totalPages === 1;
|
||||
|
||||
useEffect(() => {
|
||||
loadTeams(true);
|
||||
@ -76,19 +76,19 @@ export const TeamList = ({
|
||||
id: 'name',
|
||||
header: 'Name',
|
||||
cell: ({ cell: { value } }: Cell<'name'>) => value,
|
||||
sortType: enableSort ? 'string' : undefined,
|
||||
sortType: 'string',
|
||||
},
|
||||
{
|
||||
id: 'email',
|
||||
header: 'Email',
|
||||
cell: ({ cell: { value } }: Cell<'email'>) => value,
|
||||
sortType: enableSort ? 'string' : undefined,
|
||||
sortType: 'string',
|
||||
},
|
||||
{
|
||||
id: 'memberCount',
|
||||
header: 'Members',
|
||||
cell: ({ cell: { value } }: Cell<'memberCount'>) => value,
|
||||
sortType: enableSort ? 'number' : undefined,
|
||||
sortType: 'number',
|
||||
},
|
||||
...(displayRolePicker
|
||||
? [
|
||||
@ -155,7 +155,7 @@ export const TeamList = ({
|
||||
},
|
||||
},
|
||||
],
|
||||
[displayRolePicker, editorsCanAdmin, roleOptions, signedInUser, deleteTeam, enableSort]
|
||||
[displayRolePicker, editorsCanAdmin, roleOptions, signedInUser, deleteTeam]
|
||||
);
|
||||
|
||||
return (
|
||||
@ -185,7 +185,12 @@ export const TeamList = ({
|
||||
</LinkButton>
|
||||
</div>
|
||||
<VerticalGroup spacing={'md'}>
|
||||
<InteractiveTable columns={columns} data={teams} getRowId={(team) => String(team.id)} />
|
||||
<InteractiveTable
|
||||
columns={columns}
|
||||
data={teams}
|
||||
getRowId={(team) => String(team.id)}
|
||||
fetchData={changeSort}
|
||||
/>
|
||||
<HorizontalGroup justify="flex-end">
|
||||
<Pagination hideWhenSinglePage currentPage={page} numberOfPages={totalPages} onNavigate={changePage} />
|
||||
</HorizontalGroup>
|
||||
@ -224,6 +229,7 @@ const mapDispatchToProps = {
|
||||
deleteTeam,
|
||||
changePage,
|
||||
changeQuery,
|
||||
changeSort,
|
||||
};
|
||||
|
||||
const connector = connect(mapStateToProps, mapDispatchToProps);
|
||||
|
@ -1,17 +1,26 @@
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { FetchDataArgs } from '@grafana/ui';
|
||||
import { updateNavIndex } from 'app/core/actions';
|
||||
import { contextSrv } from 'app/core/core';
|
||||
import { accessControlQueryParam } from 'app/core/utils/accessControl';
|
||||
import { AccessControlAction, TeamMember, ThunkResult } from 'app/types';
|
||||
import { AccessControlAction, Team, TeamMember, ThunkResult } from 'app/types';
|
||||
|
||||
import { buildNavModel } from './navModel';
|
||||
import { teamGroupsLoaded, queryChanged, pageChanged, teamLoaded, teamMembersLoaded, teamsLoaded } from './reducers';
|
||||
import {
|
||||
teamGroupsLoaded,
|
||||
queryChanged,
|
||||
pageChanged,
|
||||
teamLoaded,
|
||||
teamMembersLoaded,
|
||||
teamsLoaded,
|
||||
sortChanged,
|
||||
} from './reducers';
|
||||
|
||||
export function loadTeams(initial = false): ThunkResult<void> {
|
||||
return async (dispatch, getState) => {
|
||||
const { query, page, perPage } = getState().teams;
|
||||
const { query, page, perPage, sort } = getState().teams;
|
||||
// Early return if the user cannot list teams
|
||||
if (!contextSrv.hasPermission(AccessControlAction.ActionTeamsRead)) {
|
||||
dispatch(teamsLoaded({ teams: [], totalCount: 0, page: 1, perPage, noTeams: true }));
|
||||
@ -20,7 +29,7 @@ export function loadTeams(initial = false): ThunkResult<void> {
|
||||
|
||||
const response = await getBackendSrv().get(
|
||||
'/api/teams/search',
|
||||
accessControlQueryParam({ query, page, perpage: perPage })
|
||||
accessControlQueryParam({ query, page, perpage: perPage, sort })
|
||||
);
|
||||
|
||||
// We only want to check if there is no teams on the initial request.
|
||||
@ -67,6 +76,14 @@ export function changePage(page: number): ThunkResult<void> {
|
||||
};
|
||||
}
|
||||
|
||||
export function changeSort({ sortBy }: FetchDataArgs<Team>): ThunkResult<void> {
|
||||
const sort = sortBy.length ? `${sortBy[0].id}-${sortBy[0].desc ? 'desc' : 'asc'}` : undefined;
|
||||
return async (dispatch) => {
|
||||
dispatch(sortChanged(sort));
|
||||
dispatch(loadTeams());
|
||||
};
|
||||
}
|
||||
|
||||
export function loadTeamMembers(): ThunkResult<void> {
|
||||
return async (dispatch, getStore) => {
|
||||
const team = getStore().team.team;
|
||||
|
@ -35,10 +35,13 @@ const teamsSlice = createSlice({
|
||||
pageChanged: (state, action: PayloadAction<number>): TeamsState => {
|
||||
return { ...state, page: action.payload };
|
||||
},
|
||||
sortChanged: (state, action: PayloadAction<TeamsState['sort']>): TeamsState => {
|
||||
return { ...state, sort: action.payload, page: 1 };
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { teamsLoaded, queryChanged, pageChanged } = teamsSlice.actions;
|
||||
export const { teamsLoaded, queryChanged, pageChanged, sortChanged } = teamsSlice.actions;
|
||||
|
||||
export const teamsReducer = teamsSlice.reducer;
|
||||
|
||||
|
@ -7,7 +7,7 @@ import { configureStore } from 'app/store/configureStore';
|
||||
import { Invitee, OrgUser } from 'app/types';
|
||||
|
||||
import { Props, UsersListPageUnconnected } from './UsersListPage';
|
||||
import { pageChanged } from './state/reducers';
|
||||
import { pageChanged, sortChanged } from './state/reducers';
|
||||
|
||||
jest.mock('../../core/app_events', () => ({
|
||||
emit: jest.fn(),
|
||||
@ -36,6 +36,7 @@ const setup = (propOverrides?: object) => {
|
||||
updateUser: jest.fn(),
|
||||
removeUser: jest.fn(),
|
||||
changePage: mockToolkitActionCreator(pageChanged),
|
||||
changeSort: mockToolkitActionCreator(sortChanged),
|
||||
isLoading: false,
|
||||
};
|
||||
|
||||
|
@ -12,7 +12,7 @@ import { fetchInvitees } from '../invites/state/actions';
|
||||
import { selectInvitesMatchingQuery } from '../invites/state/selectors';
|
||||
|
||||
import { UsersActionBar } from './UsersActionBar';
|
||||
import { loadUsers, removeUser, updateUser, changePage } from './state/actions';
|
||||
import { loadUsers, removeUser, updateUser, changePage, changeSort } from './state/actions';
|
||||
import { getUsers, getUsersSearchQuery } from './state/selectors';
|
||||
|
||||
function mapStateToProps(state: StoreState) {
|
||||
@ -33,6 +33,7 @@ const mapDispatchToProps = {
|
||||
loadUsers,
|
||||
fetchInvitees,
|
||||
changePage,
|
||||
changeSort,
|
||||
updateUser,
|
||||
removeUser,
|
||||
};
|
||||
@ -57,6 +58,7 @@ export const UsersListPageUnconnected = ({
|
||||
changePage,
|
||||
updateUser,
|
||||
removeUser,
|
||||
changeSort,
|
||||
}: Props) => {
|
||||
const [showInvites, setShowInvites] = useState(false);
|
||||
const externalUserMngInfoHtml = externalUserMngInfo ? renderMarkdown(externalUserMngInfo) : '';
|
||||
@ -86,6 +88,7 @@ export const UsersListPageUnconnected = ({
|
||||
orgId={contextSrv.user.orgId}
|
||||
onRoleChange={onRoleChange}
|
||||
onRemoveUser={onRemoveUser}
|
||||
fetchData={changeSort}
|
||||
changePage={changePage}
|
||||
page={page}
|
||||
totalPages={totalPages}
|
||||
|
@ -1,20 +1,21 @@
|
||||
import { debounce } from 'lodash';
|
||||
|
||||
import { getBackendSrv } from '@grafana/runtime';
|
||||
import { FetchDataArgs } from '@grafana/ui';
|
||||
import { accessControlQueryParam } from 'app/core/utils/accessControl';
|
||||
import { OrgUser } from 'app/types';
|
||||
|
||||
import { ThunkResult } from '../../../types';
|
||||
|
||||
import { usersLoaded, pageChanged, usersFetchBegin, usersFetchEnd, searchQueryChanged } from './reducers';
|
||||
import { usersLoaded, pageChanged, usersFetchBegin, usersFetchEnd, searchQueryChanged, sortChanged } from './reducers';
|
||||
|
||||
export function loadUsers(): ThunkResult<void> {
|
||||
return async (dispatch, getState) => {
|
||||
try {
|
||||
const { perPage, page, searchQuery } = getState().users;
|
||||
const { perPage, page, searchQuery, sort } = getState().users;
|
||||
const users = await getBackendSrv().get(
|
||||
`/api/org/users/search`,
|
||||
accessControlQueryParam({ perpage: perPage, page, query: searchQuery })
|
||||
accessControlQueryParam({ perpage: perPage, page, query: searchQuery, sort })
|
||||
);
|
||||
dispatch(usersLoaded(users));
|
||||
} catch (error) {
|
||||
@ -47,6 +48,15 @@ export function changePage(page: number): ThunkResult<void> {
|
||||
};
|
||||
}
|
||||
|
||||
export function changeSort({ sortBy }: FetchDataArgs<OrgUser>): ThunkResult<void> {
|
||||
const sort = sortBy.length ? `${sortBy[0].id}-${sortBy[0].desc ? 'desc' : 'asc'}` : undefined;
|
||||
return async (dispatch) => {
|
||||
dispatch(usersFetchBegin());
|
||||
dispatch(sortChanged(sort));
|
||||
dispatch(loadUsers());
|
||||
};
|
||||
}
|
||||
|
||||
export function changeSearchQuery(query: string): ThunkResult<void> {
|
||||
return async (dispatch) => {
|
||||
dispatch(usersFetchBegin());
|
||||
|
@ -51,6 +51,10 @@ const usersSlice = createSlice({
|
||||
...state,
|
||||
page: action.payload,
|
||||
}),
|
||||
sortChanged: (state, action: PayloadAction<UsersState['sort']>) => ({
|
||||
...state,
|
||||
sort: action.payload,
|
||||
}),
|
||||
usersFetchBegin: (state) => {
|
||||
return { ...state, isLoading: true };
|
||||
},
|
||||
@ -60,8 +64,15 @@ const usersSlice = createSlice({
|
||||
},
|
||||
});
|
||||
|
||||
export const { searchQueryChanged, setUsersSearchPage, usersLoaded, usersFetchBegin, usersFetchEnd, pageChanged } =
|
||||
usersSlice.actions;
|
||||
export const {
|
||||
searchQueryChanged,
|
||||
setUsersSearchPage,
|
||||
usersLoaded,
|
||||
usersFetchBegin,
|
||||
usersFetchEnd,
|
||||
pageChanged,
|
||||
sortChanged,
|
||||
} = usersSlice.actions;
|
||||
|
||||
export const usersReducer = usersSlice.reducer;
|
||||
|
||||
|
@ -63,6 +63,7 @@ export interface TeamsState {
|
||||
noTeams: boolean;
|
||||
totalPages: number;
|
||||
hasFetched: boolean;
|
||||
sort?: string;
|
||||
}
|
||||
|
||||
export interface TeamState {
|
||||
|
@ -80,6 +80,7 @@ export interface UsersState {
|
||||
page: number;
|
||||
perPage: number;
|
||||
totalPages: number;
|
||||
sort?: string;
|
||||
}
|
||||
|
||||
export interface UserSession {
|
||||
@ -124,4 +125,5 @@ export interface UserListAdminState {
|
||||
showPaging: boolean;
|
||||
filters: UserFilter[];
|
||||
isLoading: boolean;
|
||||
sort?: string;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user