diff --git a/public/app/features/admin/UserListAdminPage.tsx b/public/app/features/admin/UserListAdminPage.tsx index 45961d2d8a9..dfb7864ca73 100644 --- a/public/app/features/admin/UserListAdminPage.tsx +++ b/public/app/features/admin/UserListAdminPage.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, memo } from 'react'; +import React, { ComponentType, useEffect, useMemo, memo } from 'react'; import { css, cx } from '@emotion/css'; import { connect, ConnectedProps } from 'react-redux'; import { @@ -16,10 +16,20 @@ import Page from 'app/core/components/Page/Page'; import { TagBadge } from 'app/core/components/TagFilter/TagBadge'; import { contextSrv } from 'app/core/core'; import { getNavModel } from '../../core/selectors/navModel'; -import { AccessControlAction, StoreState, Unit, UserDTO } from '../../types'; +import { AccessControlAction, StoreState, Unit, UserDTO, UserFilter } from '../../types'; import { changeFilter, changePage, changeQuery, fetchUsers } from './state/actions'; import PageLoader from '../../core/components/PageLoader/PageLoader'; +export interface FilterProps { + filters: UserFilter[]; + onChange: (filter: any) => void; + className?: string; +} +const extraFilters: Array> = []; +export const addExtraFilters = (filter: ComponentType) => { + extraFilters.push(filter); +}; + const mapDispatchToProps = { fetchUsers, changeQuery, @@ -34,7 +44,7 @@ const mapStateToProps = (state: StoreState) => ({ showPaging: state.userListAdmin.showPaging, totalPages: state.userListAdmin.totalPages, page: state.userListAdmin.page, - filter: state.userListAdmin.filter, + filters: state.userListAdmin.filters, isLoading: state.userListAdmin.isLoading, }); @@ -55,7 +65,7 @@ const UserListAdminPageUnConnected: React.FC = ({ page, changePage, changeFilter, - filter, + filters, isLoading, }) => { const styles = useStyles2(getStyles); @@ -79,13 +89,16 @@ const UserListAdminPageUnConnected: React.FC = ({ /> changeFilter({ name: 'activeLast30Days', value })} + value={filters.find((f) => f.name === 'activeLast30Days')?.value} className={styles.filter} /> + {extraFilters.map((FilterComponent, index) => ( + + ))} {contextSrv.hasPermission(AccessControlAction.UsersCreate) && ( @@ -117,6 +130,8 @@ const UserListAdminPageUnConnected: React.FC = ({ dashboard/folder permissions.{' '} { color: ${theme.colors.text.disabled}; `, link: css` - color: ${theme.colors.text.link}; - :hover { - text-decoration: underline; - } + color: inherit; + cursor: pointer; + text-decoration: underline; `, }; }; diff --git a/public/app/features/admin/state/actions.ts b/public/app/features/admin/state/actions.ts index 71c6a0724ef..c96cf61fd9d 100644 --- a/public/app/features/admin/state/actions.ts +++ b/public/app/features/admin/state/actions.ts @@ -1,7 +1,7 @@ import config from 'app/core/config'; import { dateTimeFormat, dateTimeFormatTimeAgo } from '@grafana/data'; import { getBackendSrv, locationService } from '@grafana/runtime'; -import { ThunkResult, LdapUser, UserSession, UserDTO, AccessControlAction } from 'app/types'; +import { ThunkResult, LdapUser, UserSession, UserDTO, AccessControlAction, UserFilter } from 'app/types'; import { userAdminPageLoadedAction, @@ -258,12 +258,23 @@ export function clearUserMappingInfo(): ThunkResult { // UserListAdminPage +const getFilters = (filters: UserFilter[]) => { + return filters + .map((filter) => { + if (Array.isArray(filter.value)) { + return filter.value.map((v) => `${filter.name}=${v.value}`).join('&'); + } + return `${filter.name}=${filter.value}`; + }) + .join('&'); +}; + export function fetchUsers(): ThunkResult { return async (dispatch, getState) => { try { - const { perPage, page, query, filter } = getState().userListAdmin; + const { perPage, page, query, filters } = getState().userListAdmin; const result = await getBackendSrv().get( - `/api/users/search?perpage=${perPage}&page=${page}&query=${query}&filter=${filter}` + `/api/users/search?perpage=${perPage}&page=${page}&query=${query}&${getFilters(filters)}` ); dispatch(usersFetched(result)); } catch (error) { @@ -283,7 +294,7 @@ export function changeQuery(query: string): ThunkResult { }; } -export function changeFilter(filter: string): ThunkResult { +export function changeFilter(filter: UserFilter): ThunkResult { return async (dispatch) => { dispatch(usersFetchBegin()); dispatch(filterChanged(filter)); diff --git a/public/app/features/admin/state/reducers.test.ts b/public/app/features/admin/state/reducers.test.ts index cbd30cbe862..00052d54e4c 100644 --- a/public/app/features/admin/state/reducers.test.ts +++ b/public/app/features/admin/state/reducers.test.ts @@ -32,7 +32,7 @@ const makeInitialUserListAdminState = (): UserListAdminState => ({ perPage: 50, totalPages: 1, showPaging: false, - filter: 'all', + filters: [{ name: 'activeLast30Days', value: true }], isLoading: false, }); diff --git a/public/app/features/admin/state/reducers.ts b/public/app/features/admin/state/reducers.ts index d619e26f64b..ec025749216 100644 --- a/public/app/features/admin/state/reducers.ts +++ b/public/app/features/admin/state/reducers.ts @@ -11,6 +11,7 @@ import { UserOrg, UserSession, UserListAdminState, + UserFilter, } from 'app/types'; const initialLdapState: LdapState = { @@ -128,7 +129,7 @@ const initialUserListAdminState: UserListAdminState = { perPage: 50, totalPages: 1, showPaging: false, - filter: 'all', + filters: [{ name: 'activeLast30Days', value: false }], isLoading: false, }; @@ -171,10 +172,20 @@ export const userListAdminSlice = createSlice({ ...state, page: action.payload, }), - filterChanged: (state, action: PayloadAction) => ({ - ...state, - filter: action.payload, - }), + filterChanged: (state, action: PayloadAction) => { + const { name, value } = action.payload; + + if (state.filters.some((filter) => filter.name === name)) { + return { + ...state, + filters: state.filters.map((filter) => (filter.name === name ? { ...filter, value } : filter)), + }; + } + return { + ...state, + filters: [...state.filters, action.payload], + }; + }, }, }); diff --git a/public/app/types/user.ts b/public/app/types/user.ts index 13cffb0db4f..df2dd4129cb 100644 --- a/public/app/types/user.ts +++ b/public/app/types/user.ts @@ -1,4 +1,5 @@ import { OrgRole } from '.'; +import { SelectableValue } from '@grafana/data'; export interface OrgUser { avatarUrl: string; @@ -106,6 +107,7 @@ export interface UserAdminError { body: string; } +export type UserFilter = Record; export interface UserListAdminState { users: UserDTO[]; query: string; @@ -113,6 +115,6 @@ export interface UserListAdminState { page: number; totalPages: number; showPaging: boolean; - filter: string; + filters: UserFilter[]; isLoading: boolean; }