Admin: Enable extending filters (#39825)

* Setup extensible filters

* Fix test

* Handle filter as array

* Add className

* Abstract getFilters

* Make docs link external

* Use underline for links in tooltips instead of link color

Co-authored-by: Selene <selenepinillos@gmail.com>
This commit is contained in:
Alex Khomenko 2021-10-07 17:41:52 +03:00 committed by GitHub
parent 722c414fef
commit b5f39637d4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 61 additions and 23 deletions

View File

@ -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<ComponentType<FilterProps>> = [];
export const addExtraFilters = (filter: ComponentType<FilterProps>) => {
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<Props> = ({
page,
changePage,
changeFilter,
filter,
filters,
isLoading,
}) => {
const styles = useStyles2(getStyles);
@ -79,13 +89,16 @@ const UserListAdminPageUnConnected: React.FC<Props> = ({
/>
<RadioButtonGroup
options={[
{ label: 'All users', value: 'all' },
{ label: 'Active last 30 days', value: 'activeLast30Days' },
{ label: 'All users', value: false },
{ label: 'Active last 30 days', value: true },
]}
onChange={changeFilter}
value={filter}
onChange={(value) => changeFilter({ name: 'activeLast30Days', value })}
value={filters.find((f) => f.name === 'activeLast30Days')?.value}
className={styles.filter}
/>
{extraFilters.map((FilterComponent, index) => (
<FilterComponent key={index} filters={filters} onChange={changeFilter} className={styles.filter} />
))}
</div>
{contextSrv.hasPermission(AccessControlAction.UsersCreate) && (
<LinkButton href="admin/users/create" variant="primary">
@ -117,6 +130,8 @@ const UserListAdminPageUnConnected: React.FC<Props> = ({
dashboard/folder permissions.{' '}
<a
className={styles.link}
target="_blank"
rel="noreferrer noopener"
href={
'https://grafana.com/docs/grafana/next/enterprise/license/license-restrictions/#active-users-limit'
}
@ -327,10 +342,9 @@ const getStyles = (theme: GrafanaTheme2) => {
color: ${theme.colors.text.disabled};
`,
link: css`
color: ${theme.colors.text.link};
:hover {
text-decoration: underline;
}
color: inherit;
cursor: pointer;
text-decoration: underline;
`,
};
};

View File

@ -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<void> {
// 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<void> {
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<void> {
};
}
export function changeFilter(filter: string): ThunkResult<void> {
export function changeFilter(filter: UserFilter): ThunkResult<void> {
return async (dispatch) => {
dispatch(usersFetchBegin());
dispatch(filterChanged(filter));

View File

@ -32,7 +32,7 @@ const makeInitialUserListAdminState = (): UserListAdminState => ({
perPage: 50,
totalPages: 1,
showPaging: false,
filter: 'all',
filters: [{ name: 'activeLast30Days', value: true }],
isLoading: false,
});

View File

@ -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<string>) => ({
...state,
filter: action.payload,
}),
filterChanged: (state, action: PayloadAction<UserFilter>) => {
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],
};
},
},
});

View File

@ -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<string, string | boolean | SelectableValue[]>;
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;
}