diff --git a/public/app/features/alerting/unified/CloneRuleEditor.test.tsx b/public/app/features/alerting/unified/CloneRuleEditor.test.tsx index 408898d6566..5dab3e7b39b 100644 --- a/public/app/features/alerting/unified/CloneRuleEditor.test.tsx +++ b/public/app/features/alerting/unified/CloneRuleEditor.test.tsx @@ -1,4 +1,4 @@ -import { render, waitFor, waitForElementToBeRemoved } from '@testing-library/react'; +import { render, waitFor, waitForElementToBeRemoved, within } from '@testing-library/react'; import { setupServer } from 'msw/node'; import React from 'react'; import { FormProvider, useForm } from 'react-hook-form'; @@ -55,7 +55,6 @@ const ui = { labelValue: (idx: number) => byTestId(`label-value-${idx}`), }, loadingIndicator: byText('Loading the rule'), - loadingGroupIndicator: byText('Loading...'), }; function getProvidersWrapper() { @@ -122,7 +121,7 @@ describe('CloneRuleEditor', function () { }); await waitForElementToBeRemoved(ui.loadingIndicator.query()); - await waitForElementToBeRemoved(ui.loadingGroupIndicator.query(), { container: ui.inputs.group.get() }); + await waitForElementToBeRemoved(within(ui.inputs.group.get()).getByTestId('Spinner')); await waitFor(() => { expect(ui.inputs.name.get()).toHaveValue('First Grafana Rule (copy)'); diff --git a/public/app/features/alerting/unified/components/rule-editor/AlertRuleForm.tsx b/public/app/features/alerting/unified/components/rule-editor/AlertRuleForm.tsx index 42428c584b8..8c3a26f7966 100644 --- a/public/app/features/alerting/unified/components/rule-editor/AlertRuleForm.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/AlertRuleForm.tsx @@ -246,7 +246,6 @@ export const AlertRuleForm = ({ existing, prefill }: Props) => { <> {type === RuleFormType.grafana ? ( { + const dispatch = useDispatch(); + + // fetch the ruler rules from the database so we can figure out what other "groups" are already defined + // for our folders + useEffect(() => { + dispatch(fetchRulerRulesAction({ rulesSourceName: GRAFANA_RULES_SOURCE_NAME })); + }, [dispatch]); + const rulerRuleRequests = useUnifiedAlertingSelector((state) => state.rulerRules); const groupfoldersForGrafana = rulerRuleRequests[GRAFANA_RULES_SOURCE_NAME]; @@ -49,55 +53,33 @@ export const useGetGroupOptionsFromFolder = (folderTitle: string) => { return { groupOptions, loading: groupfoldersForGrafana?.loading }; }; -export function FolderAndGroup({ initialFolder }: FolderAndGroupProps) { +const findGroupMatchingLabel = (group: SelectableValue, query: string) => { + return group.label?.toLowerCase().includes(query.toLowerCase()); +}; + +export function FolderAndGroup() { const { formState: { errors }, watch, + setValue, control, } = useFormContext(); const styles = useStyles2(getStyles); - const dispatch = useDispatch(); const folder = watch('folder'); const group = watch('group'); - const [selectedGroup, setSelectedGroup] = useState>({ label: group, title: group }); - const initialRender = useRef(true); const { groupOptions, loading } = useGetGroupOptionsFromFolder(folder?.title ?? ''); - useEffect(() => setSelectedGroup({ label: group, title: group }), [group, setSelectedGroup]); - - useEffect(() => { - dispatch(fetchRulerRulesIfNotFetchedYet(GRAFANA_RULES_SOURCE_NAME)); - }, [dispatch]); - const resetGroup = useCallback(() => { - if (group && !initialRender.current && folder?.title) { - setSelectedGroup({ label: '', title: '' }); - } - initialRender.current = false; - }, [group, folder?.title]); - - const groupIsInGroupOptions = useCallback( - (group_: string) => { - return groupOptions.includes((groupInList: SelectableValue) => groupInList.label === group_); - }, - [groupOptions] - ); - const sliceResults = (list: Array>) => list.slice(0, SLICE_GROUP_RESULTS_TO); + setValue('group', ''); + }, [setValue]); const getOptions = useCallback( async (query: string) => { - const results = query - ? sliceResults( - groupOptions.filter((el) => { - const label = el.label ?? ''; - return label.toLowerCase().includes(query.toLowerCase()); - }) - ) - : sliceResults(groupOptions); - return results; + const results = query ? groupOptions.filter((group) => findGroupMatchingLabel(group, query)) : groupOptions; + return take(results, MAX_GROUP_RESULTS); }, [groupOptions] ); @@ -106,6 +88,8 @@ export function FolderAndGroup({ initialFolder }: FolderAndGroupProps) { return debounce(getOptions, 300, { leading: true }); }, [getOptions]); + const defaultGroupValue = group ? { value: group, label: group } : undefined; + return (
{ field.onChange({ title, uid }); - if (!groupIsInGroupOptions(selectedGroup.value ?? '')) { - resetGroup(); - } + resetGroup(); }} /> )} @@ -160,32 +142,40 @@ export function FolderAndGroup({ initialFolder }: FolderAndGroupProps) { invalid={!!errors.group?.message} > - loading ? ( - - ) : ( - ) => `${option.label}`} - placeholder={'Evaluation group name'} - onChange={(value) => { - field.onChange(value.label ?? ''); - }} - value={selectedGroup} - allowCustomValue - formatCreateLabel={(_) => '+ Add new '} - noOptionsMessage="Start typing to create evaluation group" - /> - ) - } + render={({ field: { ref, ...field }, fieldState }) => ( + { + field.onChange(group.label ?? ''); + }} + isLoading={loading} + invalid={Boolean(folder) && !group && Boolean(fieldState.error)} + loadOptions={debouncedSearch} + cacheOptions + loadingMessage={'Loading groups...'} + defaultValue={defaultGroupValue} + defaultOptions={groupOptions} + 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'} + allowCustomValue + formatCreateLabel={(_) => '+ Add new '} + noOptionsMessage="Start typing to create evaluation group" + /> + )} name="group" control={control} rules={{ diff --git a/public/app/features/alerting/unified/components/rule-editor/GrafanaEvaluationBehavior.tsx b/public/app/features/alerting/unified/components/rule-editor/GrafanaEvaluationBehavior.tsx index 94bb9d33f1a..8f74debe90b 100644 --- a/public/app/features/alerting/unified/components/rule-editor/GrafanaEvaluationBehavior.tsx +++ b/public/app/features/alerting/unified/components/rule-editor/GrafanaEvaluationBehavior.tsx @@ -11,7 +11,7 @@ import { CombinedRuleGroup, CombinedRuleNamespace } from '../../../../../types/u import { logInfo, LogMessages } from '../../Analytics'; import { useCombinedRuleNamespaces } from '../../hooks/useCombinedRuleNamespaces'; import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelector'; -import { RuleForm, RuleFormValues } from '../../types/rule-form'; +import { RuleFormValues } from '../../types/rule-form'; import { GRAFANA_RULES_SOURCE_NAME } from '../../utils/datasource'; import { parsePrometheusDuration } from '../../utils/time'; import { CollapseToggle } from '../CollapseToggle'; @@ -114,11 +114,9 @@ export const EvaluateEveryNewGroup = ({ rules }: { rules: RulerRulesConfigDTO | }; function FolderGroupAndEvaluationInterval({ - initialFolder, evaluateEvery, setEvaluateEvery, }: { - initialFolder: RuleForm | null; evaluateEvery: string; setEvaluateEvery: (value: string) => void; }) { @@ -166,7 +164,7 @@ function FolderGroupAndEvaluationInterval({ return (
- + {folderName && isEditingGroup && ( void; existing: boolean; @@ -269,11 +265,7 @@ export function GrafanaEvaluationBehavior({ // TODO remove "and alert condition" for recording rules - + {existing && (