mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
RolePicker: Optimise rendering inside lists of items (#77297)
* Role picker: Load users roles in batch * Use orgId in request * Add roles to OrgUser type * Improve loading logic * Improve loading indicator * Fix org page * Update service accounts page * Use bulk roles query for teams * Use POST requests for search * Use post request for teams * Update betterer results * Review suggestions * AdminEditOrgPage: move API calls to separate file
This commit is contained in:
@@ -1,12 +1,11 @@
|
||||
import React, { FormEvent, useCallback, useEffect, useState, useRef } from 'react';
|
||||
|
||||
import { ClickOutsideWrapper, Spinner, useStyles2, useTheme2 } from '@grafana/ui';
|
||||
import { ClickOutsideWrapper, useTheme2 } from '@grafana/ui';
|
||||
import { Role, OrgRole } from 'app/types';
|
||||
|
||||
import { RolePickerInput } from './RolePickerInput';
|
||||
import { RolePickerMenu } from './RolePickerMenu';
|
||||
import { MENU_MAX_HEIGHT, ROLE_PICKER_SUBMENU_MIN_WIDTH, ROLE_PICKER_WIDTH } from './constants';
|
||||
import { getStyles } from './styles';
|
||||
|
||||
export interface Props {
|
||||
basicRole?: OrgRole;
|
||||
@@ -50,7 +49,6 @@ export const RolePicker = ({
|
||||
const [query, setQuery] = useState('');
|
||||
const [offset, setOffset] = useState({ vertical: 0, horizontal: 0 });
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const styles = useStyles2(getStyles);
|
||||
const theme = useTheme2();
|
||||
const widthPx = typeof width === 'number' ? theme.spacing(width) : width;
|
||||
|
||||
@@ -152,15 +150,6 @@ export const RolePicker = ({
|
||||
return options;
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div style={{ maxWidth: widthPx || maxWidth, width: widthPx }}>
|
||||
<span>Loading...</span>
|
||||
<Spinner inline className={styles.loadingSpinner} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
data-testid="role-picker"
|
||||
@@ -183,6 +172,7 @@ export const RolePicker = ({
|
||||
disabled={disabled}
|
||||
showBasicRole={showBasicRole}
|
||||
width={widthPx}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
{isOpen && (
|
||||
<RolePickerMenu
|
||||
|
||||
@@ -2,7 +2,7 @@ import { css, cx } from '@emotion/css';
|
||||
import React, { FormEvent, HTMLProps, useEffect, useRef } from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { useStyles2, getInputStyles, sharedInputStyle, styleMixins, Tooltip, Icon } from '@grafana/ui';
|
||||
import { useStyles2, getInputStyles, sharedInputStyle, styleMixins, Tooltip, Icon, Spinner } from '@grafana/ui';
|
||||
|
||||
import { Role } from '../../../types';
|
||||
|
||||
@@ -19,6 +19,7 @@ interface InputProps extends HTMLProps<HTMLInputElement> {
|
||||
isFocused?: boolean;
|
||||
disabled?: boolean;
|
||||
width?: string;
|
||||
isLoading?: boolean;
|
||||
onQueryChange: (query?: string) => void;
|
||||
onOpen: (event: FormEvent<HTMLElement>) => void;
|
||||
onClose: () => void;
|
||||
@@ -32,6 +33,7 @@ export const RolePickerInput = ({
|
||||
query,
|
||||
showBasicRole,
|
||||
width,
|
||||
isLoading,
|
||||
onOpen,
|
||||
onClose,
|
||||
onQueryChange,
|
||||
@@ -63,6 +65,11 @@ export const RolePickerInput = ({
|
||||
numberOfRoles={appliedRoles.length}
|
||||
showBuiltInRole={showBasicRoleOnLabel}
|
||||
/>
|
||||
{isLoading && (
|
||||
<div className={styles.spinner}>
|
||||
<Spinner size={16} inline />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className={styles.wrapper}>
|
||||
@@ -141,22 +148,22 @@ const getRolePickerInputStyles = (
|
||||
${styleMixins.focusCss(theme.v1)}
|
||||
`,
|
||||
disabled && styles.inputDisabled,
|
||||
css`
|
||||
min-width: ${width || ROLE_PICKER_WIDTH + 'px'};
|
||||
width: ${width};
|
||||
min-height: 32px;
|
||||
height: auto;
|
||||
flex-direction: row;
|
||||
padding-right: 24px;
|
||||
max-width: 100%;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-start;
|
||||
position: relative;
|
||||
box-sizing: border-box;
|
||||
cursor: default;
|
||||
`,
|
||||
css({
|
||||
minWidth: width || ROLE_PICKER_WIDTH + 'px',
|
||||
width: width,
|
||||
minHeight: '32px',
|
||||
height: 'auto',
|
||||
flexDirection: 'row',
|
||||
paddingRight: theme.spacing(1),
|
||||
maxWidth: '100%',
|
||||
alignItems: 'center',
|
||||
display: 'flex',
|
||||
flexWrap: 'wrap',
|
||||
justifyContent: 'flex-start',
|
||||
position: 'relative',
|
||||
boxSizing: 'border-box',
|
||||
cursor: 'default',
|
||||
}),
|
||||
withPrefix &&
|
||||
css`
|
||||
padding-left: 0;
|
||||
@@ -184,6 +191,11 @@ const getRolePickerInputStyles = (
|
||||
margin-bottom: ${theme.spacing(0.5)};
|
||||
}
|
||||
`,
|
||||
spinner: css({
|
||||
display: 'flex',
|
||||
flexGrow: 1,
|
||||
justifyContent: 'flex-end',
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ export interface Props {
|
||||
orgId?: number;
|
||||
roleOptions: Role[];
|
||||
disabled?: boolean;
|
||||
roles?: Role[];
|
||||
onApplyRoles?: (newRoles: Role[]) => void;
|
||||
pendingRoles?: Role[];
|
||||
/**
|
||||
@@ -28,20 +29,26 @@ export interface Props {
|
||||
apply?: boolean;
|
||||
maxWidth?: string | number;
|
||||
width?: string | number;
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
export const TeamRolePicker = ({
|
||||
teamId,
|
||||
roleOptions,
|
||||
disabled,
|
||||
roles,
|
||||
onApplyRoles,
|
||||
pendingRoles,
|
||||
apply = false,
|
||||
maxWidth,
|
||||
width,
|
||||
isLoading,
|
||||
}: Props) => {
|
||||
const [{ loading, value: appliedRoles = [] }, getTeamRoles] = useAsyncFn(async () => {
|
||||
const [{ loading, value: appliedRoles = roles || [] }, getTeamRoles] = useAsyncFn(async () => {
|
||||
try {
|
||||
if (roles) {
|
||||
return roles;
|
||||
}
|
||||
if (apply && Boolean(pendingRoles?.length)) {
|
||||
return pendingRoles;
|
||||
}
|
||||
@@ -53,11 +60,11 @@ export const TeamRolePicker = ({
|
||||
console.error('Error loading options', e);
|
||||
}
|
||||
return [];
|
||||
}, [teamId, pendingRoles]);
|
||||
}, [teamId, pendingRoles, roles]);
|
||||
|
||||
useEffect(() => {
|
||||
getTeamRoles();
|
||||
}, [teamId, getTeamRoles, pendingRoles]);
|
||||
}, [getTeamRoles]);
|
||||
|
||||
const onRolesChange = async (roles: Role[]) => {
|
||||
if (!apply) {
|
||||
@@ -78,7 +85,7 @@ export const TeamRolePicker = ({
|
||||
onRolesChange={onRolesChange}
|
||||
roleOptions={roleOptions}
|
||||
appliedRoles={appliedRoles}
|
||||
isLoading={loading}
|
||||
isLoading={loading || isLoading}
|
||||
disabled={disabled}
|
||||
basicRoleDisabled={true}
|
||||
canUpdateRoles={canUpdateRoles}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { fetchUserRoles, updateUserRoles } from './api';
|
||||
|
||||
export interface Props {
|
||||
basicRole: OrgRole;
|
||||
roles?: Role[];
|
||||
userId: number;
|
||||
orgId?: number;
|
||||
onBasicRoleChange: (newRole: OrgRole) => void;
|
||||
@@ -32,10 +33,12 @@ export interface Props {
|
||||
pendingRoles?: Role[];
|
||||
maxWidth?: string | number;
|
||||
width?: string | number;
|
||||
isLoading?: boolean;
|
||||
}
|
||||
|
||||
export const UserRolePicker = ({
|
||||
basicRole,
|
||||
roles,
|
||||
userId,
|
||||
orgId,
|
||||
onBasicRoleChange,
|
||||
@@ -48,9 +51,13 @@ export const UserRolePicker = ({
|
||||
pendingRoles,
|
||||
maxWidth,
|
||||
width,
|
||||
isLoading,
|
||||
}: Props) => {
|
||||
const [{ loading, value: appliedRoles = [] }, getUserRoles] = useAsyncFn(async () => {
|
||||
const [{ loading, value: appliedRoles = roles || [] }, getUserRoles] = useAsyncFn(async () => {
|
||||
try {
|
||||
if (roles) {
|
||||
return roles;
|
||||
}
|
||||
if (apply && Boolean(pendingRoles?.length)) {
|
||||
return pendingRoles;
|
||||
}
|
||||
@@ -63,14 +70,14 @@ export const UserRolePicker = ({
|
||||
console.error('Error loading options');
|
||||
}
|
||||
return [];
|
||||
}, [orgId, userId, pendingRoles]);
|
||||
}, [orgId, userId, pendingRoles, roles]);
|
||||
|
||||
useEffect(() => {
|
||||
// only load roles when there is an Org selected
|
||||
if (orgId) {
|
||||
getUserRoles();
|
||||
}
|
||||
}, [orgId, getUserRoles, pendingRoles]);
|
||||
}, [getUserRoles, orgId]);
|
||||
|
||||
const onRolesChange = async (roles: Role[]) => {
|
||||
if (!apply) {
|
||||
@@ -92,7 +99,7 @@ export const UserRolePicker = ({
|
||||
onRolesChange={onRolesChange}
|
||||
onBasicRoleChange={onBasicRoleChange}
|
||||
roleOptions={roleOptions}
|
||||
isLoading={loading}
|
||||
isLoading={loading || isLoading}
|
||||
disabled={disabled}
|
||||
basicRoleDisabled={basicRoleDisabled}
|
||||
basicRoleDisabledMessage={basicRoleDisabledMessage}
|
||||
|
||||
Reference in New Issue
Block a user