diff --git a/packages/grafana-data/src/types/select.ts b/packages/grafana-data/src/types/select.ts index 42132d1fdd1..52f5f097dc6 100644 --- a/packages/grafana-data/src/types/select.ts +++ b/packages/grafana-data/src/types/select.ts @@ -13,5 +13,6 @@ export interface SelectableValue { title?: string; // Optional component that will be shown together with other options. Does not get past any props. component?: React.ComponentType; + isDisabled?: boolean; [key: string]: any; } diff --git a/packages/grafana-ui/src/components/Select/SelectBase.tsx b/packages/grafana-ui/src/components/Select/SelectBase.tsx index d8f531c766f..ac82de6afe6 100644 --- a/packages/grafana-ui/src/components/Select/SelectBase.tsx +++ b/packages/grafana-ui/src/components/Select/SelectBase.tsx @@ -357,7 +357,7 @@ export function SelectBase({ return ; }, SingleValue(props: any) { - return ; + return ; }, SelectContainer, MultiValueContainer: MultiValueContainer, diff --git a/packages/grafana-ui/src/components/Select/types.ts b/packages/grafana-ui/src/components/Select/types.ts index 12b8714e4ba..e3fe5c6b766 100644 --- a/packages/grafana-ui/src/components/Select/types.ts +++ b/packages/grafana-ui/src/components/Select/types.ts @@ -90,7 +90,7 @@ export interface SelectCommonProps { virtualized?: boolean; /** Sets the width to a multiple of 8px. Should only be used with inline forms. Setting width of the container is preferred in other cases.*/ width?: number | 'auto'; - isOptionDisabled?: () => boolean; + isOptionDisabled?: (option: SelectableValue) => boolean; /** allowCustomValue must be enabled. Determines whether the "create new" option should be displayed based on the current input value, select value and options array. */ isValidNewOption?: ( inputValue: string, diff --git a/public/app/features/alerting/unified/components/rule-editor/FolderAndGroup.tsx b/public/app/features/alerting/unified/components/rule-editor/FolderAndGroup.tsx index 512f1b3fc9a..7468a50e883 100644 --- a/public/app/features/alerting/unified/components/rule-editor/FolderAndGroup.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/FolderAndGroup.tsx @@ -5,9 +5,10 @@ import { useFormContext } from 'react-hook-form'; import { GrafanaTheme2, SelectableValue } from '@grafana/data'; import { Stack } from '@grafana/experimental'; -import { AsyncSelect, Field, InputControl, Label, LoadingPlaceholder, useStyles2 } from '@grafana/ui'; +import { AsyncSelect, Badge, Field, InputControl, Label, LoadingPlaceholder, useStyles2 } from '@grafana/ui'; import { contextSrv } from 'app/core/core'; import { AccessControlAction, useDispatch } from 'app/types'; +import { CombinedRuleGroup } from 'app/types/unified-alerting'; import { useCombinedRuleNamespaces } from '../../hooks/useCombinedRuleNamespaces'; import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector'; @@ -34,21 +35,29 @@ export const useGetGroupOptionsFromFolder = (folderTitle: string) => { const grafanaFolders = useCombinedRuleNamespaces(GRAFANA_RULES_SOURCE_NAME); const folderGroups = grafanaFolders.find((f) => f.name === folderTitle)?.groups ?? []; - const nonProvisionedGroups = folderGroups.filter((g) => { - return g.rules.every( - (r) => isGrafanaRulerRule(r.rulerRule) && Boolean(r.rulerRule.grafana_alert.provenance) === false - ); - }); - - const groupOptions = nonProvisionedGroups.map>((group) => ({ - label: group.name, - value: group.name, - description: group.interval ?? MINUTE, - })); + const groupOptions = folderGroups + .map>((group) => ({ + label: group.name, + value: group.name, + description: group.interval ?? MINUTE, + // we include provisioned folders, but disable the option to select them + isDisabled: isProvisionedGroup(group), + })) + .sort(sortByLabel); return { groupOptions, loading: groupfoldersForGrafana?.loading }; }; +const isProvisionedGroup = (group: CombinedRuleGroup) => { + return group.rules.some( + (rule) => isGrafanaRulerRule(rule.rulerRule) && Boolean(rule.rulerRule.grafana_alert.provenance) === true + ); +}; + +const sortByLabel = (a: SelectableValue, b: SelectableValue) => { + return a.label?.localeCompare(b.label ?? '') || 0; +}; + export function FolderAndGroup({ initialFolder }: FolderAndGroupProps) { const { formState: { errors }, @@ -97,6 +106,7 @@ export function FolderAndGroup({ initialFolder }: FolderAndGroupProps) { }) ) : sliceResults(groupOptions); + return results; }, [groupOptions] @@ -160,7 +170,7 @@ export function FolderAndGroup({ initialFolder }: FolderAndGroupProps) { invalid={!!errors.group?.message} > + render={({ field: { ref, ...field }, fieldState }) => loading ? ( ) : ( @@ -169,12 +179,23 @@ export function FolderAndGroup({ initialFolder }: FolderAndGroupProps) { inputId="group" key={`my_unique_select_key__${selectedGroup?.title ?? ''}`} {...field} - invalid={Boolean(folder) && !selectedGroup.title} + invalid={Boolean(folder) && !selectedGroup.title && Boolean(fieldState.error)} loadOptions={debouncedSearch} loadingMessage={'Loading groups...'} defaultOptions={groupOptions} defaultValue={selectedGroup} - getOptionLabel={(option: SelectableValue) => `${option.label}`} + getOptionLabel={(option: SelectableValue) => ( +
+ {option.label} + {/* making the assumption here that it's provisioned when it's disabled, should probably change this */} + {option.isDisabled && ( + <> + {' '} + + + )} +
+ )} placeholder={'Evaluation group name'} onChange={(value) => { field.onChange(value.label ?? ''); @@ -209,6 +230,7 @@ const getStyles = (theme: GrafanaTheme2) => ({ `, formInput: css` width: 275px; + & + & { margin-left: ${theme.spacing(3)}; }