diff --git a/public/app/core/components/RolePicker/RolePickerMenu.tsx b/public/app/core/components/RolePicker/RolePickerMenu.tsx
index 16b6236c8e0..43cfc5b75a0 100644
--- a/public/app/core/components/RolePicker/RolePickerMenu.tsx
+++ b/public/app/core/components/RolePicker/RolePickerMenu.tsx
@@ -1,5 +1,6 @@
import { css, cx } from '@emotion/css';
import React, { FormEvent, useEffect, useRef, useState } from 'react';
+import { v4 as uuidv4 } from 'uuid';
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
import {
@@ -22,6 +23,7 @@ import { MENU_MAX_HEIGHT, ROLE_PICKER_SUBMENU_MIN_WIDTH } from './constants';
enum GroupType {
fixed = 'fixed',
custom = 'custom',
+ plugin = 'plugin',
}
const BasicRoles = Object.values(OrgRole);
@@ -87,7 +89,33 @@ export const RolePickerMenu = ({
const customRoles = options.filter(filterCustomRoles).sort(sortRolesByName);
const fixedRoles = options.filter(filterFixedRoles).sort(sortRolesByName);
- const optionGroups = getOptionGroups(options);
+ const pluginRoles = options.filter(filterPluginsRoles).sort(sortRolesByName);
+ const optionGroups = {
+ fixed: convertRolesToGroupOptions(fixedRoles).sort((a, b) => a.name.localeCompare(b.name)),
+ custom: convertRolesToGroupOptions(customRoles).sort((a, b) => a.name.localeCompare(b.name)),
+ plugin: convertRolesToGroupOptions(pluginRoles).sort((a, b) => a.name.localeCompare(b.name)),
+ };
+
+ const rolesCollection = {
+ fixed: {
+ groupType: GroupType.fixed,
+ optionGroup: optionGroups.fixed,
+ renderedName: `Fixed roles`,
+ roles: fixedRoles,
+ },
+ custom: {
+ groupType: GroupType.custom,
+ optionGroup: optionGroups.custom,
+ renderedName: `Custom roles`,
+ roles: customRoles,
+ },
+ pluginRoles: {
+ groupType: GroupType.plugin,
+ optionGroup: optionGroups.plugin,
+ renderedName: `Plugin roles`,
+ roles: pluginRoles,
+ },
+ };
const getSelectedGroupOptions = (group: string) => {
const selectedGroupOptions = [];
@@ -123,16 +151,17 @@ export const RolePickerMenu = ({
const group = optionGroups[groupType].find((g) => {
return g.value === value;
});
+
+ if (!group) {
+ return;
+ }
+
if (groupSelected(groupType, value) || groupPartiallySelected(groupType, value)) {
- if (group) {
- setSelectedOptions(selectedOptions.filter((role) => !group.options.find((option) => role.uid === option.uid)));
- }
+ setSelectedOptions(selectedOptions.filter((role) => !group.options.find((option) => role.uid === option.uid)));
} else {
- if (group) {
- const groupOptions = group.options.filter((role) => role.delegatable);
- const restOptions = selectedOptions.filter((role) => !group.options.find((option) => role.uid === option.uid));
- setSelectedOptions([...restOptions, ...groupOptions]);
- }
+ const groupOptions = group.options.filter((role) => role.delegatable);
+ const restOptions = selectedOptions.filter((role) => !group.options.find((option) => role.uid === option.uid));
+ setSelectedOptions([...restOptions, ...groupOptions]);
}
};
@@ -200,96 +229,33 @@ export const RolePickerMenu = ({
/>
)}
- {!!fixedRoles.length && (
-
-
Fixed roles
-
- {showGroups && !!optionGroups.fixed.length
- ? optionGroups.fixed.map((option, i) => (
- 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 && (
- 0}
- />
- )}
-
- ))
- : fixedRoles.map((option, i) => (
- opt.uid === option.uid))}
- disabled={isNotDelegatable(option)}
- onChange={onChange}
- hideDescription
- />
- ))}
-
-
- )}
- {!!customRoles.length && (
-
-
Custom roles
-
- {showGroups && !!optionGroups.custom.length
- ? optionGroups.custom.map((option, i) => (
- 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 && (
- 0}
- />
- )}
-
- ))
- : customRoles.map((option, i) => (
- opt.uid === option.uid))}
- disabled={isNotDelegatable(option)}
- onChange={onChange}
- hideDescription
- />
- ))}
-
-
- )}
+ {Object.entries(rolesCollection).map(([groupId, collection]) => {
+ return (
+ groupSelected(collection.groupType, group)}
+ groupPartiallySelected={(group: string) => groupPartiallySelected(collection.groupType, group)}
+ onChange={(group: string) => onGroupChange(collection.groupType, group)}
+ onOpenSubMenuRMGS={(group: string) => onOpenSubMenu(collection.groupType, group)}
+ onCloseSubMenu={onCloseSubMenu}
+ subMenuNode={subMenuNode?.current!}
+ showSubMenu={showSubMenu}
+ openedMenuGroup={openedMenuGroup}
+ subMenuOptions={subMenuOptions}
+ selectedOptions={selectedOptions}
+ onChangeSubMenu={onChange}
+ onClearSubMenu={onClearSubMenu}
+ showOnLeftSubMenu={offset.horizontal > 0}
+ >
+ );
+ })}
@@ -307,45 +273,28 @@ export const RolePickerMenu = ({
);
};
-const filterCustomRoles = (option: Role) => !option.name?.startsWith('fixed:');
+const filterCustomRoles = (option: Role) => !option.name?.startsWith('fixed:') && !option.name.startsWith('plugins:');
const filterFixedRoles = (option: Role) => option.name?.startsWith('fixed:');
+const filterPluginsRoles = (option: Role) => option.name?.startsWith('plugins:');
-const getOptionGroups = (options: Role[]) => {
+const convertRolesToGroupOptions = (roles: Role[]) => {
const groupsMap: { [key: string]: Role[] } = {};
- const customGroupsMap: { [key: string]: Role[] } = {};
- options.forEach((role) => {
- const m = role.name.startsWith('fixed:') ? groupsMap : customGroupsMap;
+ roles.forEach((role) => {
const groupName = getRoleGroup(role);
- if (!m[groupName]) {
- m[groupName] = [];
+ if (!groupsMap[groupName]) {
+ groupsMap[groupName] = [];
}
- m[groupName].push(role);
+ groupsMap[groupName].push(role);
});
-
- const groups = [];
- for (const groupName of Object.keys(groupsMap)) {
- const groupOptions = groupsMap[groupName].sort(sortRolesByName);
- groups.push({
+ const groups = Object.entries(groupsMap).map(([groupName, roles]) => {
+ return {
name: fixedRoleGroupNames[groupName] || capitalize(groupName),
value: groupName,
- options: groupOptions,
- });
- }
-
- 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)),
- };
+ options: roles.sort(sortRolesByName),
+ uid: uuidv4(),
+ };
+ });
+ return groups;
};
interface RolePickerSubMenuProps {
@@ -467,6 +416,111 @@ export const RoleMenuOption = React.forwardRef;
+ onChange: (value: string) => void;
+ onOpenSubMenuRMGS: (value: string) => void;
+ onCloseSubMenu?: (value: string) => void;
+ groupSelected: (group: string) => boolean;
+ groupPartiallySelected: (group: string) => boolean;
+ disabled?: boolean;
+ subMenuNode?: HTMLDivElement;
+ showSubMenu: boolean;
+ openedMenuGroup: string;
+ subMenuOptions: Role[];
+ selectedOptions: Role[];
+ onChangeSubMenu: (option: Role) => void;
+ onClearSubMenu: () => void;
+ showOnLeftSubMenu: boolean;
+}
+
+export const RoleMenuGroupsSection = React.forwardRef(
+ (
+ {
+ roles,
+ renderedName,
+ menuSectionStyle,
+ groupHeaderStyle,
+ optionBodyStyle,
+ showGroups,
+ optionGroups,
+ onChange,
+ groupSelected,
+ groupPartiallySelected,
+ onOpenSubMenuRMGS,
+ onCloseSubMenu,
+ subMenuNode,
+ showSubMenu,
+ openedMenuGroup,
+ subMenuOptions,
+ selectedOptions,
+ onChangeSubMenu,
+ onClearSubMenu,
+ showOnLeftSubMenu,
+ },
+ _ref
+ ) => {
+ return (
+
+ {roles.length > 0 && (
+
+
{renderedName}
+
+ {showGroups && !!optionGroups?.length
+ ? optionGroups.map((option) => (
+
+ {showSubMenu && openedMenuGroup === option.value && (
+
+ )}
+
+ ))
+ : roles.map((option) => (
+
opt.uid === option.uid))}
+ disabled={isNotDelegatable(option)}
+ onChange={onChangeSubMenu}
+ hideDescription
+ />
+ ))}
+
+ )}
+
+ );
+ }
+);
+
+RoleMenuGroupsSection.displayName = 'RoleMenuGroupsSection';
+
interface RoleMenuGroupsOptionProps {
data: SelectableValue;
onChange: (value: string) => void;