mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
RBAC: Display groups for custom roles (#54020)
* RolePicker: Default to "Other" for roles without group * RolePicker: Add GroupType enum and calculate group options based on group type * RolePicker: Display groups for custom roles * RolePicker: Remove unused code * RolePicker: Restructure Co-authored-by: Alex Khomenko <Clarity-89@users.noreply.github.com>
This commit is contained in:
parent
f91f05f32c
commit
ef25d297d6
@ -19,6 +19,11 @@ import { OrgRole, Role } from 'app/types';
|
||||
|
||||
import { MENU_MAX_HEIGHT } from './constants';
|
||||
|
||||
enum GroupType {
|
||||
fixed = 'fixed',
|
||||
custom = 'custom',
|
||||
}
|
||||
|
||||
const BasicRoles = Object.values(OrgRole);
|
||||
const BasicRoleOption: Array<SelectableValue<OrgRole>> = BasicRoles.map((r) => ({
|
||||
label: r,
|
||||
@ -94,15 +99,15 @@ export const RolePickerMenu = ({
|
||||
return selectedGroupOptions;
|
||||
};
|
||||
|
||||
const groupSelected = (group: string) => {
|
||||
const groupSelected = (groupType: GroupType, group: string) => {
|
||||
const selectedGroupOptions = getSelectedGroupOptions(group);
|
||||
const groupOptions = optionGroups.find((g) => g.value === group);
|
||||
const groupOptions = optionGroups[groupType].find((g) => g.value === group);
|
||||
return selectedGroupOptions.length > 0 && selectedGroupOptions.length >= groupOptions!.options.length;
|
||||
};
|
||||
|
||||
const groupPartiallySelected = (group: string) => {
|
||||
const groupPartiallySelected = (groupType: GroupType, group: string) => {
|
||||
const selectedGroupOptions = getSelectedGroupOptions(group);
|
||||
const groupOptions = optionGroups.find((g) => g.value === group);
|
||||
const groupOptions = optionGroups[groupType].find((g) => g.value === group);
|
||||
return selectedGroupOptions.length > 0 && selectedGroupOptions.length < groupOptions!.options.length;
|
||||
};
|
||||
|
||||
@ -114,11 +119,11 @@ export const RolePickerMenu = ({
|
||||
}
|
||||
};
|
||||
|
||||
const onGroupChange = (value: string) => {
|
||||
const group = optionGroups.find((g) => {
|
||||
const onGroupChange = (groupType: GroupType, value: string) => {
|
||||
const group = optionGroups[groupType].find((g) => {
|
||||
return g.value === value;
|
||||
});
|
||||
if (groupSelected(value) || groupPartiallySelected(value)) {
|
||||
if (groupSelected(groupType, value) || groupPartiallySelected(groupType, value)) {
|
||||
if (group) {
|
||||
setSelectedOptions(selectedOptions.filter((role) => !group.options.find((option) => role.uid === option.uid)));
|
||||
}
|
||||
@ -131,10 +136,10 @@ export const RolePickerMenu = ({
|
||||
}
|
||||
};
|
||||
|
||||
const onOpenSubMenu = (value: string) => {
|
||||
const onOpenSubMenu = (groupType: GroupType, value: string) => {
|
||||
setOpenedMenuGroup(value);
|
||||
setShowSubMenu(true);
|
||||
const group = optionGroups.find((g) => {
|
||||
const group = optionGroups[groupType].find((g) => {
|
||||
return g.value === value;
|
||||
});
|
||||
if (group) {
|
||||
@ -165,12 +170,6 @@ export const RolePickerMenu = ({
|
||||
};
|
||||
|
||||
const onUpdateInternal = () => {
|
||||
const selectedCustomRoles: string[] = [];
|
||||
// TODO: needed?
|
||||
for (const key in selectedOptions) {
|
||||
const roleUID = selectedOptions[key]?.uid;
|
||||
selectedCustomRoles.push(roleUID);
|
||||
}
|
||||
onUpdate(selectedOptions, selectedBuiltInRole);
|
||||
};
|
||||
|
||||
@ -201,68 +200,93 @@ export const RolePickerMenu = ({
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{!!fixedRoles.length &&
|
||||
(showGroups && !!optionGroups.length ? (
|
||||
<div className={customStyles.menuSection}>
|
||||
<div className={customStyles.groupHeader}>Fixed roles</div>
|
||||
<div className={styles.optionBody}>
|
||||
{optionGroups.map((option, i) => (
|
||||
<RoleMenuGroupOption
|
||||
data={option}
|
||||
key={i}
|
||||
isSelected={groupSelected(option.value) || groupPartiallySelected(option.value)}
|
||||
partiallySelected={groupPartiallySelected(option.value)}
|
||||
disabled={option.options?.every(isNotDelegatable)}
|
||||
onChange={onGroupChange}
|
||||
onOpenSubMenu={onOpenSubMenu}
|
||||
onCloseSubMenu={onCloseSubMenu}
|
||||
root={subMenuNode?.current!}
|
||||
isFocused={showSubMenu && openedMenuGroup === option.value}
|
||||
>
|
||||
{showSubMenu && openedMenuGroup === option.value && (
|
||||
<RolePickerSubMenu
|
||||
options={subMenuOptions}
|
||||
selectedOptions={selectedOptions}
|
||||
onSelect={onChange}
|
||||
onClear={onClearSubMenu}
|
||||
showOnLeft={offset.horizontal > 0}
|
||||
/>
|
||||
)}
|
||||
</RoleMenuGroupOption>
|
||||
))}
|
||||
</div>
|
||||
{!!fixedRoles.length && (
|
||||
<div className={customStyles.menuSection}>
|
||||
<div className={customStyles.groupHeader}>Fixed roles</div>
|
||||
<div className={styles.optionBody}>
|
||||
{showGroups && !!optionGroups.fixed.length
|
||||
? optionGroups.fixed.map((option, i) => (
|
||||
<RoleMenuGroupOption
|
||||
data={option}
|
||||
key={i}
|
||||
isSelected={
|
||||
groupSelected(GroupType.fixed, option.value) ||
|
||||
groupPartiallySelected(GroupType.fixed, option.value)
|
||||
}
|
||||
partiallySelected={groupPartiallySelected(GroupType.fixed, option.value)}
|
||||
disabled={option.options?.every(isNotDelegatable)}
|
||||
onChange={(group: string) => onGroupChange(GroupType.fixed, group)}
|
||||
onOpenSubMenu={(group: string) => onOpenSubMenu(GroupType.fixed, group)}
|
||||
onCloseSubMenu={onCloseSubMenu}
|
||||
root={subMenuNode?.current!}
|
||||
isFocused={showSubMenu && openedMenuGroup === option.value}
|
||||
>
|
||||
{showSubMenu && openedMenuGroup === option.value && (
|
||||
<RolePickerSubMenu
|
||||
options={subMenuOptions}
|
||||
selectedOptions={selectedOptions}
|
||||
onSelect={onChange}
|
||||
onClear={onClearSubMenu}
|
||||
showOnLeft={offset.horizontal > 0}
|
||||
/>
|
||||
)}
|
||||
</RoleMenuGroupOption>
|
||||
))
|
||||
: fixedRoles.map((option, i) => (
|
||||
<RoleMenuOption
|
||||
data={option}
|
||||
key={i}
|
||||
isSelected={!!(option.uid && !!selectedOptions.find((opt) => opt.uid === option.uid))}
|
||||
disabled={isNotDelegatable(option)}
|
||||
onChange={onChange}
|
||||
hideDescription
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className={customStyles.menuSection}>
|
||||
<div className={customStyles.groupHeader}>Fixed roles</div>
|
||||
<div className={styles.optionBody}>
|
||||
{fixedRoles.map((option, i) => (
|
||||
<RoleMenuOption
|
||||
data={option}
|
||||
key={i}
|
||||
isSelected={!!(option.uid && !!selectedOptions.find((opt) => opt.uid === option.uid))}
|
||||
disabled={isNotDelegatable(option)}
|
||||
onChange={onChange}
|
||||
hideDescription
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
{!!customRoles.length && (
|
||||
<div>
|
||||
<div className={customStyles.menuSection}>
|
||||
<div className={customStyles.groupHeader}>Custom roles</div>
|
||||
<div className={styles.optionBody}>
|
||||
{customRoles.map((option, i) => (
|
||||
<RoleMenuOption
|
||||
data={option}
|
||||
key={i}
|
||||
isSelected={!!(option.uid && !!selectedOptions.find((opt) => opt.uid === option.uid))}
|
||||
disabled={isNotDelegatable(option)}
|
||||
onChange={onChange}
|
||||
hideDescription
|
||||
/>
|
||||
))}
|
||||
{showGroups && !!optionGroups.custom.length
|
||||
? optionGroups.custom.map((option, i) => (
|
||||
<RoleMenuGroupOption
|
||||
data={option}
|
||||
key={i}
|
||||
isSelected={
|
||||
groupSelected(GroupType.custom, option.value) ||
|
||||
groupPartiallySelected(GroupType.custom, option.value)
|
||||
}
|
||||
partiallySelected={groupPartiallySelected(GroupType.custom, option.value)}
|
||||
disabled={option.options?.every(isNotDelegatable)}
|
||||
onChange={(group: string) => onGroupChange(GroupType.custom, group)}
|
||||
onOpenSubMenu={(group: string) => onOpenSubMenu(GroupType.custom, group)}
|
||||
onCloseSubMenu={onCloseSubMenu}
|
||||
root={subMenuNode?.current!}
|
||||
isFocused={showSubMenu && openedMenuGroup === option.value}
|
||||
>
|
||||
{showSubMenu && openedMenuGroup === option.value && (
|
||||
<RolePickerSubMenu
|
||||
options={subMenuOptions}
|
||||
selectedOptions={selectedOptions}
|
||||
onSelect={onChange}
|
||||
onClear={onClearSubMenu}
|
||||
showOnLeft={offset.horizontal > 0}
|
||||
/>
|
||||
)}
|
||||
</RoleMenuGroupOption>
|
||||
))
|
||||
: customRoles.map((option, i) => (
|
||||
<RoleMenuOption
|
||||
data={option}
|
||||
key={i}
|
||||
isSelected={!!(option.uid && !!selectedOptions.find((opt) => opt.uid === option.uid))}
|
||||
disabled={isNotDelegatable(option)}
|
||||
onChange={onChange}
|
||||
hideDescription
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@ -288,15 +312,14 @@ const filterFixedRoles = (option: Role) => option.name?.startsWith('fixed:');
|
||||
|
||||
const getOptionGroups = (options: Role[]) => {
|
||||
const groupsMap: { [key: string]: Role[] } = {};
|
||||
const customGroupsMap: { [key: string]: Role[] } = {};
|
||||
options.forEach((role) => {
|
||||
if (role.name.startsWith('fixed:')) {
|
||||
const groupName = getRoleGroup(role);
|
||||
if (groupsMap[groupName]) {
|
||||
groupsMap[groupName].push(role);
|
||||
} else {
|
||||
groupsMap[groupName] = [role];
|
||||
}
|
||||
const m = role.name.startsWith('fixed:') ? groupsMap : customGroupsMap;
|
||||
const groupName = getRoleGroup(role);
|
||||
if (!m[groupName]) {
|
||||
m[groupName] = [];
|
||||
}
|
||||
m[groupName].push(role);
|
||||
});
|
||||
|
||||
const groups = [];
|
||||
@ -308,7 +331,21 @@ const getOptionGroups = (options: Role[]) => {
|
||||
options: groupOptions,
|
||||
});
|
||||
}
|
||||
return groups.sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
const customGroups = [];
|
||||
for (const groupName of Object.keys(customGroupsMap)) {
|
||||
const groupOptions = customGroupsMap[groupName].sort(sortRolesByName);
|
||||
customGroups.push({
|
||||
name: capitalize(groupName),
|
||||
value: groupName,
|
||||
options: groupOptions,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
fixed: groups.sort((a, b) => a.name.localeCompare(b.name)),
|
||||
custom: customGroups.sort((a, b) => a.name.localeCompare(b.name)),
|
||||
};
|
||||
};
|
||||
|
||||
interface RolePickerSubMenuProps {
|
||||
@ -527,7 +564,7 @@ export const RoleMenuGroupOption = React.forwardRef<HTMLDivElement, RoleMenuGrou
|
||||
RoleMenuGroupOption.displayName = 'RoleMenuGroupOption';
|
||||
|
||||
const getRoleGroup = (role: Role) => {
|
||||
return role.group ?? 'Other';
|
||||
return role.group || 'Other';
|
||||
};
|
||||
|
||||
const capitalize = (s: string): string => {
|
||||
|
Loading…
Reference in New Issue
Block a user