diff --git a/public/app/features/users/UsersListPage.test.tsx b/public/app/features/users/UsersListPage.test.tsx index fff47a6432b..396a5e66a00 100644 --- a/public/app/features/users/UsersListPage.test.tsx +++ b/public/app/features/users/UsersListPage.test.tsx @@ -5,7 +5,7 @@ import { Invitee, OrgUser } from 'app/types'; // import { getMockUser } from './__mocks__/userMocks'; import { NavModel } from '@grafana/data'; import { mockToolkitActionCreator } from 'test/core/redux/mocks'; -import { setUsersSearchQuery } from './state/reducers'; +import { setUsersSearchPage, setUsersSearchQuery } from './state/reducers'; jest.mock('../../core/app_events', () => ({ emit: jest.fn(), @@ -24,12 +24,14 @@ const setup = (propOverrides?: object) => { users: [] as OrgUser[], invitees: [] as Invitee[], searchQuery: '', + searchPage: 1, externalUserMngInfo: '', loadInvitees: jest.fn(), loadUsers: jest.fn(), updateUser: jest.fn(), removeUser: jest.fn(), setUsersSearchQuery: mockToolkitActionCreator(setUsersSearchQuery), + setUsersSearchPage: mockToolkitActionCreator(setUsersSearchPage), hasFetched: false, }; diff --git a/public/app/features/users/UsersListPage.tsx b/public/app/features/users/UsersListPage.tsx index a474046521d..8c588a378ea 100644 --- a/public/app/features/users/UsersListPage.tsx +++ b/public/app/features/users/UsersListPage.tsx @@ -2,6 +2,7 @@ import React, { PureComponent } from 'react'; import { hot } from 'react-hot-loader'; import { connect } from 'react-redux'; import { NavModel, renderMarkdown } from '@grafana/data'; +import { HorizontalGroup, Pagination, VerticalGroup } from '@grafana/ui'; import Page from 'app/core/components/Page/Page'; import UsersActionBar from './UsersActionBar'; @@ -10,19 +11,21 @@ import InviteesTable from './InviteesTable'; import { Invitee, OrgUser, OrgRole } from 'app/types'; import { loadInvitees, loadUsers, removeUser, updateUser } from './state/actions'; import { getNavModel } from 'app/core/selectors/navModel'; -import { getInvitees, getUsers, getUsersSearchQuery } from './state/selectors'; -import { setUsersSearchQuery } from './state/reducers'; +import { getInvitees, getUsers, getUsersSearchQuery, getUsersSearchPage } from './state/selectors'; +import { setUsersSearchQuery, setUsersSearchPage } from './state/reducers'; export interface Props { navModel: NavModel; invitees: Invitee[]; users: OrgUser[]; searchQuery: string; + searchPage: number; externalUserMngInfo: string; hasFetched: boolean; loadUsers: typeof loadUsers; loadInvitees: typeof loadInvitees; setUsersSearchQuery: typeof setUsersSearchQuery; + setUsersSearchPage: typeof setUsersSearchPage; updateUser: typeof updateUser; removeUser: typeof removeUser; } @@ -31,6 +34,8 @@ export interface State { showInvites: boolean; } +const pageLimit = 30; + export class UsersListPage extends PureComponent { externalUserMngInfoHtml: string; @@ -71,18 +76,35 @@ export class UsersListPage extends PureComponent { })); }; + getPaginatedUsers = (users: OrgUser[]) => { + const offset = (this.props.searchPage - 1) * pageLimit; + return users.slice(offset, offset + pageLimit); + }; + renderTable() { - const { invitees, users } = this.props; + const { invitees, users, setUsersSearchPage } = this.props; + const paginatedUsers = this.getPaginatedUsers(users); + const totalPages = Math.ceil(users.length / pageLimit); if (this.state.showInvites) { return ; } else { return ( - this.onRoleChange(role, user)} - onRemoveUser={(user) => this.props.removeUser(user.userId)} - /> + + this.onRoleChange(role, user)} + onRemoveUser={(user) => this.props.removeUser(user.userId)} + /> + + + + ); } } @@ -112,6 +134,7 @@ function mapStateToProps(state: any) { navModel: getNavModel(state.navIndex, 'users'), users: getUsers(state.users), searchQuery: getUsersSearchQuery(state.users), + searchPage: getUsersSearchPage(state.users), invitees: getInvitees(state.users), externalUserMngInfo: state.users.externalUserMngInfo, hasFetched: state.users.hasFetched, @@ -122,6 +145,7 @@ const mapDispatchToProps = { loadUsers, loadInvitees, setUsersSearchQuery, + setUsersSearchPage, updateUser, removeUser, }; diff --git a/public/app/features/users/__snapshots__/UsersListPage.test.tsx.snap b/public/app/features/users/__snapshots__/UsersListPage.test.tsx.snap index c8836666c59..08b3ba0ec22 100644 --- a/public/app/features/users/__snapshots__/UsersListPage.test.tsx.snap +++ b/public/app/features/users/__snapshots__/UsersListPage.test.tsx.snap @@ -20,11 +20,25 @@ exports[`Render should render List page 1`] = ` onShowInvites={[Function]} showInvites={false} /> - + + + + + + `; diff --git a/public/app/features/users/state/reducers.ts b/public/app/features/users/state/reducers.ts index 6e3a36c35a9..c43cbd17783 100644 --- a/public/app/features/users/state/reducers.ts +++ b/public/app/features/users/state/reducers.ts @@ -7,6 +7,7 @@ export const initialState: UsersState = { invitees: [] as Invitee[], users: [] as OrgUser[], searchQuery: '', + searchPage: 1, canInvite: !config.externalUserMngLinkName, externalUserMngInfo: config.externalUserMngInfo, externalUserMngLinkName: config.externalUserMngLinkName, @@ -25,12 +26,16 @@ const usersSlice = createSlice({ return { ...state, hasFetched: true, invitees: action.payload }; }, setUsersSearchQuery: (state, action: PayloadAction): UsersState => { - return { ...state, searchQuery: action.payload }; + // reset searchPage otherwise search results won't appear + return { ...state, searchQuery: action.payload, searchPage: initialState.searchPage }; + }, + setUsersSearchPage: (state, action: PayloadAction): UsersState => { + return { ...state, searchPage: action.payload }; }, }, }); -export const { inviteesLoaded, setUsersSearchQuery, usersLoaded } = usersSlice.actions; +export const { inviteesLoaded, setUsersSearchQuery, setUsersSearchPage, usersLoaded } = usersSlice.actions; export const usersReducer = usersSlice.reducer; diff --git a/public/app/features/users/state/selectors.ts b/public/app/features/users/state/selectors.ts index e487cdea5c0..361ff059c3e 100644 --- a/public/app/features/users/state/selectors.ts +++ b/public/app/features/users/state/selectors.ts @@ -18,3 +18,4 @@ export const getInvitees = (state: UsersState) => { export const getInviteesCount = (state: UsersState) => state.invitees.length; export const getUsersSearchQuery = (state: UsersState) => state.searchQuery; +export const getUsersSearchPage = (state: UsersState) => state.searchPage; diff --git a/public/app/types/user.ts b/public/app/types/user.ts index a6c1d17ddd3..076b5977182 100644 --- a/public/app/types/user.ts +++ b/public/app/types/user.ts @@ -61,6 +61,7 @@ export interface UsersState { users: OrgUser[]; invitees: Invitee[]; searchQuery: string; + searchPage: number; canInvite: boolean; externalUserMngLinkUrl: string; externalUserMngLinkName: string;