diff --git a/public/app/features/alerting/unified/RuleEditorExisting.test.tsx b/public/app/features/alerting/unified/RuleEditorExisting.test.tsx
index 87143007b66..ee8a14a749f 100644
--- a/public/app/features/alerting/unified/RuleEditorExisting.test.tsx
+++ b/public/app/features/alerting/unified/RuleEditorExisting.test.tsx
@@ -142,7 +142,7 @@ describe('RuleEditor grafana managed rules', () => {
{
annotations: { description: 'some description', summary: 'some summary' },
labels: { severity: 'warn', team: 'the a-team' },
- for: '5m',
+ for: '1m',
grafana_alert: {
uid,
namespace_uid: 'abcd',
@@ -208,7 +208,7 @@ describe('RuleEditor grafana managed rules', () => {
{
annotations: { description: 'some description', summary: 'some summary', custom: 'value' },
labels: { severity: 'warn', team: 'the a-team', custom: 'value' },
- for: '5m',
+ for: '1m',
grafana_alert: {
uid,
condition: 'B',
diff --git a/public/app/features/alerting/unified/RuleEditorGrafanaRules.test.tsx b/public/app/features/alerting/unified/RuleEditorGrafanaRules.test.tsx
index d4f6f031e6f..096c594ae83 100644
--- a/public/app/features/alerting/unified/RuleEditorGrafanaRules.test.tsx
+++ b/public/app/features/alerting/unified/RuleEditorGrafanaRules.test.tsx
@@ -111,7 +111,7 @@ describe('RuleEditor grafana managed rules', () => {
{
annotations: { description: 'some description', summary: 'some summary' },
labels: { severity: 'warn', team: 'the a-team' },
- for: '5m',
+ for: '1m',
grafana_alert: {
uid: '23',
namespace_uid: 'abcd',
@@ -133,7 +133,7 @@ describe('RuleEditor grafana managed rules', () => {
{
annotations: { description: 'some description', summary: 'some summary' },
labels: { severity: 'warn', team: 'the a-team' },
- for: '5m',
+ for: '1m',
grafana_alert: {
uid: '23',
namespace_uid: 'b',
@@ -209,7 +209,7 @@ describe('RuleEditor grafana managed rules', () => {
{
annotations: { description: 'some description' },
labels: { severity: 'warn' },
- for: '5m',
+ for: '1m',
grafana_alert: {
condition: 'B',
data: getDefaultQueries(),
diff --git a/public/app/features/alerting/unified/RuleList.test.tsx b/public/app/features/alerting/unified/RuleList.test.tsx
index a02a5b6a67a..06d1e9c8256 100644
--- a/public/app/features/alerting/unified/RuleList.test.tsx
+++ b/public/app/features/alerting/unified/RuleList.test.tsx
@@ -706,6 +706,7 @@ describe('RuleList', () => {
await userEvent.clear(ui.editGroupModal.ruleGroupInput.get());
await userEvent.type(ui.editGroupModal.ruleGroupInput.get(), 'super group');
+ await userEvent.clear(ui.editGroupModal.intervalInput.get());
await userEvent.type(ui.editGroupModal.intervalInput.get(), '5m');
// submit, check that appropriate calls were made
@@ -743,6 +744,8 @@ describe('RuleList', () => {
// make changes to form
await userEvent.clear(ui.editGroupModal.ruleGroupInput.get());
await userEvent.type(ui.editGroupModal.ruleGroupInput.get(), 'super group');
+
+ await userEvent.clear(ui.editGroupModal.intervalInput.get());
await userEvent.type(ui.editGroupModal.intervalInput.get(), '5m');
// submit, check that appropriate calls were made
@@ -773,6 +776,7 @@ describe('RuleList', () => {
testCase('edit lotex group eval interval, no renaming', async () => {
// make changes to form
+ await userEvent.clear(ui.editGroupModal.intervalInput.get());
await userEvent.type(ui.editGroupModal.intervalInput.get(), '5m');
// submit, check that appropriate calls were made
diff --git a/public/app/features/alerting/unified/components/rule-editor/EvaluationGroupQuickPick.test.tsx b/public/app/features/alerting/unified/components/rule-editor/EvaluationGroupQuickPick.test.tsx
new file mode 100644
index 00000000000..ec9b1ceb0ef
--- /dev/null
+++ b/public/app/features/alerting/unified/components/rule-editor/EvaluationGroupQuickPick.test.tsx
@@ -0,0 +1,46 @@
+import { screen } from '@testing-library/dom';
+import { render } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import React from 'react';
+
+import { getEvaluationGroupOptions, EvaluationGroupQuickPick } from './EvaluationGroupQuickPick';
+
+describe('EvaluationGroupQuickPick', () => {
+ it('should render the correct default preset, set active element and allow selecting another option', async () => {
+ const onSelect = jest.fn();
+ render();
+
+ const shouldHaveButtons = ['10s', '30s', '1m', '5m', '10m', '15m', '30m', '1h'];
+ const shouldNotHaveButtons = ['0s', '2h'];
+
+ shouldHaveButtons.forEach((name) => {
+ expect(screen.getByRole('option', { name })).toBeInTheDocument();
+ });
+
+ shouldNotHaveButtons.forEach((name) => {
+ expect(screen.queryByRole('option', { name })).not.toBeInTheDocument();
+ });
+
+ expect(screen.getByRole('option', { selected: true })).toHaveTextContent('10m');
+
+ await userEvent.click(screen.getByRole('option', { name: '30m' }));
+ expect(onSelect).toHaveBeenCalledWith('30m');
+ });
+});
+
+describe('getEvaluationGroupOptions', () => {
+ it('should return the correct default options', () => {
+ const options = getEvaluationGroupOptions();
+ expect(options).toEqual(['10s', '30s', '1m', '5m', '10m', '15m', '30m', '1h']);
+ });
+
+ it('should return the correct options when minInterval is set within set of defaults', () => {
+ const options = getEvaluationGroupOptions('1m0s');
+ expect(options).toEqual(['1m', '5m', '10m', '15m', '30m', '1h', '2h', '4h']);
+ });
+
+ it('should return the correct options when minInterval is set outside set of defaults', () => {
+ const options = getEvaluationGroupOptions('12h');
+ expect(options).toEqual(['12h', '1d', '1d12h', '2d', '2d12h', '3d', '3d12h', '4d']);
+ });
+});
diff --git a/public/app/features/alerting/unified/components/rule-editor/EvaluationGroupQuickPick.tsx b/public/app/features/alerting/unified/components/rule-editor/EvaluationGroupQuickPick.tsx
new file mode 100644
index 00000000000..a92c2826676
--- /dev/null
+++ b/public/app/features/alerting/unified/components/rule-editor/EvaluationGroupQuickPick.tsx
@@ -0,0 +1,72 @@
+import { last, times } from 'lodash';
+import React from 'react';
+
+import { config } from '@grafana/runtime';
+import { Button, Stack } from '@grafana/ui';
+
+import { formatPrometheusDuration, parsePrometheusDuration, safeParsePrometheusDuration } from '../../utils/time';
+
+const MIN_INTERVAl = config.unifiedAlerting.minInterval ?? '10s';
+export const getEvaluationGroupOptions = (minInterval = MIN_INTERVAl) => {
+ const MIN_OPTIONS_TO_SHOW = 8;
+ const DEFAULT_INTERVAL_OPTIONS: number[] = [
+ parsePrometheusDuration('10s'),
+ parsePrometheusDuration('30s'),
+ parsePrometheusDuration('1m'),
+ parsePrometheusDuration('5m'),
+ parsePrometheusDuration('10m'),
+ parsePrometheusDuration('15m'),
+ parsePrometheusDuration('30m'),
+ parsePrometheusDuration('1h'),
+ ];
+
+ // 10s for OSS and 1m0s for Grafana Cloud
+ const minEvaluationIntervalMillis = safeParsePrometheusDuration(minInterval);
+
+ /**
+ * 1. make sure we always show at least 8 options to the user
+ * 2. find the default interval closest to the configured minInterval
+ * 3. if we have fewer than 8 options, we basically double the last interval until we have 8 options
+ */
+ const head = DEFAULT_INTERVAL_OPTIONS.filter((millis) => minEvaluationIntervalMillis <= millis);
+
+ const tail = times(MIN_OPTIONS_TO_SHOW - head.length, (index: number) => {
+ const lastInterval = last(head) ?? minEvaluationIntervalMillis;
+ const multiplier = head.length === 0 ? 1 : 2; // if the head is empty we start with the min interval and multiply it only once :)
+ return lastInterval * multiplier * (index + 1);
+ });
+
+ return [...head, ...tail].map(formatPrometheusDuration);
+};
+
+export const QUICK_PICK_OPTIONS = getEvaluationGroupOptions(MIN_INTERVAl);
+
+interface Props {
+ currentInterval: string;
+ onSelect: (interval: string) => void;
+}
+
+/**
+ * Allow a quick selection of group evaluation intervals, based on the configured "unifiedAlerting.minInterval" value
+ * ie. [1m, 2m, 5m, 10m, 15m] etc.
+ */
+export const EvaluationGroupQuickPick = ({ currentInterval, onSelect }: Props) => (
+
+ {QUICK_PICK_OPTIONS.map((interval) => {
+ const isActive = currentInterval === interval;
+
+ return (
+
+ );
+ })}
+
+);
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 f36ed62992a..e2e59d7c7b7 100644
--- a/public/app/features/alerting/unified/components/rule-editor/FolderAndGroup.tsx
+++ b/public/app/features/alerting/unified/components/rule-editor/FolderAndGroup.tsx
@@ -17,11 +17,12 @@ import { useUnifiedAlertingSelector } from '../../hooks/useUnifiedAlertingSelect
import { fetchRulerRulesAction } from '../../state/actions';
import { RuleFormValues } from '../../types/rule-form';
import { GRAFANA_RULES_SOURCE_NAME } from '../../utils/datasource';
-import { MINUTE } from '../../utils/rule-form';
+import { DEFAULT_GROUP_EVALUATION_INTERVAL } from '../../utils/rule-form';
import { isGrafanaRulerRule } from '../../utils/rules';
import { ProvisioningBadge } from '../Provisioning';
import { evaluateEveryValidationOptions } from '../rules/EditRuleGroupModal';
+import { EvaluationGroupQuickPick } from './EvaluationGroupQuickPick';
import { containsSlashes, Folder, RuleFolderPicker } from './RuleFolderPicker';
import { checkForPathSeparator } from './util';
@@ -48,7 +49,7 @@ export const useFolderGroupOptions = (folderUid: string, enableProvisionedGroups
return {
label: group.name,
value: group.name,
- description: group.interval ?? MINUTE,
+ description: group.interval ?? DEFAULT_GROUP_EVALUATION_INTERVAL,
// we include provisioned folders, but disable the option to select them
isDisabled: !enableProvisionedGroups ? isProvisioned : false,
isProvisioned: isProvisioned,
@@ -357,12 +358,17 @@ function EvaluationGroupCreationModal({
};
const formAPI = useForm({
- defaultValues: { group: '', evaluateEvery: '' },
+ defaultValues: { group: '', evaluateEvery: DEFAULT_GROUP_EVALUATION_INTERVAL },
mode: 'onChange',
shouldFocusError: true,
});
- const { register, handleSubmit, formState, getValues } = formAPI;
+ const { register, handleSubmit, formState, setValue, getValues, watch: watchGroupFormValues } = formAPI;
+ const evaluationInterval = watchGroupFormValues('evaluateEvery');
+
+ const setEvaluationInterval = (interval: string) => {
+ setValue('evaluateEvery', interval, { shouldValidate: true });
+ };
return (
Evaluation group name}
error={formState.errors.group?.message}
- invalid={!!formState.errors.group}
+ invalid={Boolean(formState.errors.group)}
>
+
}
>
-
+
+
+
+
+
+