mirror of
https://github.com/grafana/grafana.git
synced 2025-02-15 10:03:33 -06:00
* Very simple role picker * Style radio button * Separate component for the built-in roles selector * Custom component instead of Select * refactor * Custom input for role picker * Refactor * Able to select built-in role * Add checkboxes for role selector * Filter out fixed and internal roles * Add action buttons * Implement role search * Fix selecting roles * Pass custom roles to update * User role picker * Some UX work on role picker * Clear search query on close * Blur input when closed * Add roles counter * Refactor * Add disabled state for picker * Adjust disabled styles * Replace ChangeOrgButton with role picker on admin/users page * Remove unused code * Apply suggestions from code review Suggestions from the @Clarity-89 Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com> * Refactor: fix some errors after applying review suggestions * Show fixed roles in the picker * Show applied fixed roles * Fix role counter * Fix checkbox selection * Use specific Role type for menu options * Fix menu when roles list is empty * Fix radio button name * Make fixed roles from built-in role disabled * Make whole menu scrollable * Add BuiltInRole type * Simplify appliedRoles * Simplify options and props * Do not select and disable inherited fixed roles * Enable selecting fixed role * Add description tooltip * Fix role param name * Export common input styles from grafana/ui * Add ValueContainer * Use value container * Refactor appliedRoles logic * Optimise role rendering * Display selected roles * Fix tooltip position * Use OrgRole type * Optimise role rendering * Use radio button from grafana UI * Submenu WIP * Role picker submenu WIP * Hide role description * Tweak styles * Implement submenu selection * Disable role selection if it's inherited * Show new role picker only in Enterprise * Fix types * Use orgid when fetching/updating roles * Use orgId in all access control requests * Styles for partially checked checkbox * Tweak group option styles * Role picker menu: refactor * Reorganize roles in menu * Fix input behaviour * Hide groups on search * Remove unused components * Refactor * Fix group selection * Remove icons from role tags * Add spacing for menu sections * Rename clear all to clear in submenu * Tweak menu width * Show changes in the input when selecting roles * Exclude inherited roles from selection * Increase menu height * Change built-in role in input on select * Include inherited roles to the built-in role selection * refcator import * Refactor role picker to be able to pass roles and builtin roles getters * Add role picker to the org users page * Show inherited builtin roles in the popup * Filter out managed roles * Fix displaying initial builtin roles * Show tooltip only for non-builtin roles * Set min width for focused input * Do not disable inherited roles (by design) * Only show picker if access control enabled * Fix tests * Only close menu on click outside or on indicator click * Open submenu on hover * Don't search on empty query * Do not open/close menu on click * Refactor * Apply suggestions from code review Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com> * Fix formatting * Apply suggestions * Add more space for close menu sign * Tune tooltip styles * Move tooltip to the right side of option * Use info sign instead of question Co-authored-by: Clarity-89 <homes89@ukr.net> Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com>
142 lines
4.0 KiB
TypeScript
142 lines
4.0 KiB
TypeScript
import React, { FormEvent, useCallback, useEffect, useState } from 'react';
|
|
import { ClickOutsideWrapper } from '@grafana/ui';
|
|
import { RolePickerMenu } from './RolePickerMenu';
|
|
import { RolePickerInput } from './RolePickerInput';
|
|
import { Role, OrgRole } from 'app/types';
|
|
|
|
export interface Props {
|
|
builtInRole: OrgRole;
|
|
getRoles: () => Promise<Role[]>;
|
|
getRoleOptions: () => Promise<Role[]>;
|
|
getBuiltinRoles: () => Promise<Record<string, Role[]>>;
|
|
onRolesChange: (newRoles: string[]) => void;
|
|
onBuiltinRoleChange: (newRole: OrgRole) => void;
|
|
disabled?: boolean;
|
|
}
|
|
|
|
export const RolePicker = ({
|
|
builtInRole,
|
|
getRoles,
|
|
getRoleOptions,
|
|
getBuiltinRoles,
|
|
onRolesChange,
|
|
onBuiltinRoleChange,
|
|
disabled,
|
|
}: Props): JSX.Element | null => {
|
|
const [isOpen, setOpen] = useState(false);
|
|
const [roleOptions, setRoleOptions] = useState<Role[]>([]);
|
|
const [appliedRoles, setAppliedRoles] = useState<Role[]>([]);
|
|
const [selectedRoles, setSelectedRoles] = useState<Role[]>([]);
|
|
const [selectedBuiltInRole, setSelectedBuiltInRole] = useState<OrgRole>(builtInRole);
|
|
const [builtInRoles, setBuiltinRoles] = useState<Record<string, Role[]>>({});
|
|
const [query, setQuery] = useState('');
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
async function fetchOptions() {
|
|
try {
|
|
let options = await getRoleOptions();
|
|
setRoleOptions(options.filter((option) => !option.name?.startsWith('managed:')));
|
|
|
|
const builtInRoles = await getBuiltinRoles();
|
|
setBuiltinRoles(builtInRoles);
|
|
|
|
const userRoles = await getRoles();
|
|
setAppliedRoles(userRoles);
|
|
setSelectedRoles(userRoles);
|
|
} catch (e) {
|
|
// TODO handle error
|
|
console.error('Error loading options');
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
}
|
|
|
|
fetchOptions();
|
|
}, [getRoles, getRoleOptions, getBuiltinRoles, builtInRole]);
|
|
|
|
const onOpen = useCallback(
|
|
(event: FormEvent<HTMLElement>) => {
|
|
if (!disabled) {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
setOpen(true);
|
|
}
|
|
},
|
|
[setOpen, disabled]
|
|
);
|
|
|
|
const onClose = useCallback(() => {
|
|
setOpen(false);
|
|
setQuery('');
|
|
setSelectedRoles(appliedRoles);
|
|
setSelectedBuiltInRole(builtInRole);
|
|
}, [appliedRoles, builtInRole]);
|
|
|
|
// Only call onClose if menu is open. Prevent unnecessary calls for multiple pickers on the page.
|
|
const onClickOutside = () => isOpen && onClose();
|
|
|
|
const onInputChange = (query?: string) => {
|
|
if (query) {
|
|
setQuery(query);
|
|
} else {
|
|
setQuery('');
|
|
}
|
|
};
|
|
|
|
const onSelect = (roles: Role[]) => {
|
|
setSelectedRoles(roles);
|
|
};
|
|
|
|
const onBuiltInRoleSelect = (role: OrgRole) => {
|
|
setSelectedBuiltInRole(role);
|
|
};
|
|
|
|
const onUpdate = (newBuiltInRole: OrgRole, newRoles: string[]) => {
|
|
onBuiltinRoleChange(newBuiltInRole);
|
|
onRolesChange(newRoles);
|
|
setOpen(false);
|
|
setQuery('');
|
|
};
|
|
|
|
const getOptions = () => {
|
|
if (query && query.trim() !== '') {
|
|
return roleOptions.filter((option) => option.name?.toLowerCase().includes(query.toLowerCase()));
|
|
}
|
|
return roleOptions;
|
|
};
|
|
|
|
if (isLoading) {
|
|
return null;
|
|
}
|
|
|
|
return (
|
|
<div data-testid="role-picker" style={{ position: 'relative' }}>
|
|
<ClickOutsideWrapper onClick={onClickOutside}>
|
|
<RolePickerInput
|
|
builtInRole={selectedBuiltInRole}
|
|
appliedRoles={selectedRoles}
|
|
query={query}
|
|
onQueryChange={onInputChange}
|
|
onOpen={onOpen}
|
|
onClose={onClose}
|
|
isFocused={isOpen}
|
|
disabled={disabled}
|
|
/>
|
|
{isOpen && (
|
|
<RolePickerMenu
|
|
options={getOptions()}
|
|
builtInRole={selectedBuiltInRole}
|
|
builtInRoles={builtInRoles}
|
|
appliedRoles={appliedRoles}
|
|
onBuiltInRoleSelect={onBuiltInRoleSelect}
|
|
onSelect={onSelect}
|
|
onUpdate={onUpdate}
|
|
showGroups={query.length === 0 || query.trim() === ''}
|
|
/>
|
|
)}
|
|
</ClickOutsideWrapper>
|
|
</div>
|
|
);
|
|
};
|