replace evaluation inputs with group dropdown
This commit is contained in:
@@ -7,12 +7,10 @@ import { Trans, t } from '@grafana/i18n';
|
||||
import {
|
||||
Combobox,
|
||||
ComboboxOption,
|
||||
Field,
|
||||
Icon,
|
||||
InlineField,
|
||||
InlineFieldRow,
|
||||
Input,
|
||||
Select,
|
||||
Stack,
|
||||
Text,
|
||||
useStyles2,
|
||||
@@ -24,29 +22,15 @@ import { reducerTypes, thresholdFunctions } from 'app/features/expressions/types
|
||||
import { isRangeEvaluator } from 'app/features/expressions/utils/expressionTypes';
|
||||
|
||||
import { RuleFormType, RuleFormValues } from '../types/rule-form';
|
||||
import { safeParsePrometheusDuration } from '../utils/time';
|
||||
|
||||
import { getPendingPeriodQuickOptions } from './rule-editor/DurationQuickPick';
|
||||
// no custom pending/eval parsing here; use defaults from form
|
||||
import { EvaluationGroupFieldRow } from './rule-editor/EvaluationGroupFieldRow';
|
||||
|
||||
export function RuleConditionSection({ type }: { type: RuleFormType }) {
|
||||
const base = useStyles2(getStyles);
|
||||
const { register, watch, setValue } = useFormContext<RuleFormValues>();
|
||||
const evaluateEvery = watch('evaluateEvery') || '5m';
|
||||
const { watch } = useFormContext<RuleFormValues>();
|
||||
const evaluateFor = watch('evaluateFor') || '0s';
|
||||
const pendingOptions = getPendingPeriodQuickOptions(evaluateEvery);
|
||||
const [customMode, setCustomMode] = useState(false);
|
||||
const isCustomSelected = customMode;
|
||||
const customDelay = isCustomSelected ? evaluateFor : '';
|
||||
const evalMs = safeParsePrometheusDuration(evaluateEvery);
|
||||
const customMs = safeParsePrometheusDuration(customDelay || '0s');
|
||||
const isCustomInvalid = isCustomSelected && (evalMs <= 0 || customMs <= 0 || customMs % evalMs !== 0);
|
||||
|
||||
let fireSelectLabel: string = evaluateFor;
|
||||
if (isCustomSelected) {
|
||||
fireSelectLabel = customDelay || pendingOptions[1] || '1m';
|
||||
} else if (evaluateFor === '0s') {
|
||||
fireSelectLabel = t('alerting.duration.immediately', 'immediately');
|
||||
}
|
||||
watch('folder');
|
||||
|
||||
type LocalSimpleCondition = { whenField?: string; evaluator: { params: number[]; type: EvalFunction } };
|
||||
const [simpleCondition, setSimpleCondition] = useState<LocalSimpleCondition>({
|
||||
@@ -145,72 +129,7 @@ export function RuleConditionSection({ type }: { type: RuleFormType }) {
|
||||
</InlineField>
|
||||
</InlineFieldRow>
|
||||
|
||||
<div className={base.paragraphRow}>
|
||||
<Text>
|
||||
<Trans i18nKey="alerting.simplified.evaluation.every">Evaluate the rule every</Trans>
|
||||
</Text>
|
||||
<Input width={8} {...register('evaluateEvery')} />
|
||||
<Text>.</Text>
|
||||
<Text>
|
||||
<Trans i18nKey="alerting.simplified.evaluation.wait-prefix">Wait</Trans>
|
||||
</Text>
|
||||
<Input width={8} value={evaluateFor} onChange={(e) => setValue('evaluateFor', e.currentTarget.value)} />
|
||||
{isCustomSelected && (
|
||||
<Field
|
||||
noMargin
|
||||
className={base.inlineField}
|
||||
invalid={isCustomInvalid || undefined}
|
||||
error={
|
||||
isCustomInvalid
|
||||
? t(
|
||||
'alerting.simplified.evaluation.custom-delay-invalid',
|
||||
'The delay must be a multiple of the evaluation frequency.'
|
||||
)
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<Input width={8} value={customDelay} onChange={(e) => setValue('evaluateFor', e.currentTarget.value)} />
|
||||
</Field>
|
||||
)}
|
||||
<Text>
|
||||
<Trans i18nKey="alerting.simplified.evaluation.wait-suffix">
|
||||
after the condition is breached before firing.
|
||||
</Trans>
|
||||
</Text>
|
||||
<Text>
|
||||
<Trans i18nKey="alerting.simplified.evaluation.fire">Fire the alert rule</Trans>
|
||||
</Text>
|
||||
<Select
|
||||
width={20}
|
||||
value={{
|
||||
value: isCustomSelected ? 'custom' : evaluateFor,
|
||||
label: fireSelectLabel,
|
||||
}}
|
||||
onChange={(v: SelectableValue<string>) => {
|
||||
const val = v?.value;
|
||||
if (val === 'custom') {
|
||||
setCustomMode(true);
|
||||
const defCustom = pendingOptions[1] || evaluateEvery || '1m';
|
||||
if (!evaluateFor || pendingOptions.includes(evaluateFor)) {
|
||||
setValue('evaluateFor', defCustom);
|
||||
}
|
||||
return;
|
||||
}
|
||||
setCustomMode(false);
|
||||
setValue('evaluateFor', val || '0s');
|
||||
}}
|
||||
options={[
|
||||
{ value: '0s', label: t('alerting.duration.immediately', 'immediately') },
|
||||
...pendingOptions.filter((d) => d !== '0s').map((d) => ({ value: d, label: d })),
|
||||
{ value: 'custom', label: t('alerting.simplified.evaluation.custom-delay', 'with custom delay of') },
|
||||
]}
|
||||
/>
|
||||
<Text>
|
||||
<Trans i18nKey="alerting.simplified.evaluation.after-breached">
|
||||
after the condition is initially breached.
|
||||
</Trans>
|
||||
</Text>
|
||||
</div>
|
||||
<EvaluationGroupFieldRow enableProvisionedGroups={false} />
|
||||
|
||||
{evaluateFor === '0s' && (
|
||||
<Stack direction="row" gap={0.5} alignItems="center">
|
||||
|
||||
+191
@@ -0,0 +1,191 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { uniqueId } from 'lodash';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { Controller, useFormContext } from 'react-hook-form';
|
||||
|
||||
import { GrafanaTheme2, SelectableValue } from '@grafana/data';
|
||||
import { Trans, t } from '@grafana/i18n';
|
||||
import { Box, Button, Field, Select, Stack, Text, useStyles2 } from '@grafana/ui';
|
||||
import { RulerRuleGroupDTO } from 'app/types/unified-alerting-dto';
|
||||
|
||||
import { useFetchGroupsForFolder } from '../../hooks/useFetchGroupsForFolder';
|
||||
import { DEFAULT_GROUP_EVALUATION_INTERVAL } from '../../rule-editor/formDefaults';
|
||||
import { RuleFormValues } from '../../types/rule-form';
|
||||
import { isProvisionedRuleGroup } from '../../utils/rules';
|
||||
import { ProvisioningBadge } from '../Provisioning';
|
||||
|
||||
import { EvaluationGroupCreationModal } from './GrafanaEvaluationBehavior';
|
||||
|
||||
export function EvaluationGroupFieldRow({ enableProvisionedGroups }: { enableProvisionedGroups: boolean }) {
|
||||
const styles = useStyles2(getStyles);
|
||||
|
||||
const {
|
||||
watch,
|
||||
setValue,
|
||||
getValues,
|
||||
formState: { errors },
|
||||
control,
|
||||
} = useFormContext<RuleFormValues>();
|
||||
|
||||
const [group, folder] = watch(['group', 'folder']);
|
||||
const { currentData: rulerNamespace, isLoading: loadingGroups } = useFetchGroupsForFolder(folder?.uid ?? '');
|
||||
|
||||
const collator = useMemo(() => new Intl.Collator(), []);
|
||||
type GroupOption = SelectableValue<string> & { isProvisioned?: boolean };
|
||||
const groupOptions = useMemo<GroupOption[]>(() => {
|
||||
if (!rulerNamespace) {
|
||||
return [];
|
||||
}
|
||||
const folderGroups = Object.values(rulerNamespace).flat();
|
||||
return folderGroups
|
||||
.map<GroupOption>((g: RulerRuleGroupDTO) => {
|
||||
const provisioned = isProvisionedRuleGroup(g);
|
||||
return {
|
||||
label: g.name,
|
||||
value: g.name,
|
||||
description: g.interval ?? DEFAULT_GROUP_EVALUATION_INTERVAL,
|
||||
isDisabled: !enableProvisionedGroups ? provisioned : false,
|
||||
isProvisioned: provisioned,
|
||||
};
|
||||
})
|
||||
.sort((a, b) => collator.compare(a.label ?? '', b.label ?? ''));
|
||||
}, [collator, enableProvisionedGroups, rulerNamespace]);
|
||||
|
||||
const defaultGroupValue = group ? { value: group, label: group } : undefined;
|
||||
|
||||
const [isCreatingEvaluationGroup, setIsCreatingEvaluationGroup] = useState(false);
|
||||
const onOpenEvaluationGroupCreationModal = () => setIsCreatingEvaluationGroup(true);
|
||||
|
||||
const handleEvalGroupCreation = (groupName: string, evaluationInterval: string) => {
|
||||
setValue('group', groupName);
|
||||
setValue('evaluateEvery', evaluationInterval);
|
||||
setIsCreatingEvaluationGroup(false);
|
||||
};
|
||||
|
||||
const label = !folder?.uid
|
||||
? t(
|
||||
'alerting.rule-form.evaluation.select-folder-before',
|
||||
'Select a folder before setting evaluation group and interval'
|
||||
)
|
||||
: t('alerting.rule-form.evaluation.evaluation-group-and-interval', 'Evaluation group and interval');
|
||||
|
||||
return (
|
||||
<Stack alignItems="center">
|
||||
<div style={{ width: 420 }}>
|
||||
<Field
|
||||
noMargin
|
||||
label={label}
|
||||
data-testid="group-picker"
|
||||
className={styles.formInput}
|
||||
error={errors.group?.message}
|
||||
invalid={!!errors.group?.message}
|
||||
htmlFor="group"
|
||||
>
|
||||
<Controller
|
||||
render={({ field: { ref, ...field }, fieldState }) => (
|
||||
<Select
|
||||
disabled={!folder?.uid || loadingGroups}
|
||||
inputId="group"
|
||||
key={uniqueId()}
|
||||
{...field}
|
||||
onChange={(group) => {
|
||||
field.onChange(group.label ?? '');
|
||||
}}
|
||||
isLoading={loadingGroups}
|
||||
invalid={Boolean(folder?.uid) && !group && Boolean(fieldState.error)}
|
||||
cacheOptions
|
||||
loadingMessage={t(
|
||||
'alerting.grafana-evaluation-behavior-step.loadingMessage-loading-groups',
|
||||
'Loading groups...'
|
||||
)}
|
||||
defaultValue={defaultGroupValue}
|
||||
options={groupOptions}
|
||||
getOptionLabel={(option: GroupOption) => (
|
||||
<div>
|
||||
<span>{option.label}</span>
|
||||
{option.isProvisioned && (
|
||||
<>
|
||||
{' '}
|
||||
<ProvisioningBadge />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
placeholder={t(
|
||||
'alerting.grafana-evaluation-behavior-step.placeholder-select-an-evaluation-group',
|
||||
'Select an evaluation group...'
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
name="group"
|
||||
control={control}
|
||||
rules={{
|
||||
required: {
|
||||
value: true,
|
||||
message: t(
|
||||
'alerting.grafana-evaluation-behavior-step.message.must-enter-a-group-name',
|
||||
'Must enter a group name'
|
||||
),
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</Field>
|
||||
</div>
|
||||
<Box gap={1} display={'flex'} alignItems={'center'}>
|
||||
<Text color="secondary">
|
||||
<Trans i18nKey="alerting.grafana-evaluation-behavior-step.or">or</Trans>
|
||||
</Text>
|
||||
<Button
|
||||
onClick={onOpenEvaluationGroupCreationModal}
|
||||
type="button"
|
||||
icon="plus"
|
||||
fill="outline"
|
||||
variant="secondary"
|
||||
disabled={!folder?.uid}
|
||||
data-testid={'new-evaluation-group-button'}
|
||||
>
|
||||
<Trans i18nKey="alerting.rule-form.evaluation.new-group">New evaluation group</Trans>
|
||||
</Button>
|
||||
</Box>
|
||||
{isCreatingEvaluationGroup && (
|
||||
<EvaluationGroupCreationModal
|
||||
onCreate={handleEvalGroupCreation}
|
||||
onClose={() => setIsCreatingEvaluationGroup(false)}
|
||||
groupfoldersForGrafana={rulerNamespace}
|
||||
/>
|
||||
)}
|
||||
{getValues('group') && getValues('evaluateEvery') && (
|
||||
<div className={styles.evaluationContainer}>
|
||||
<Stack direction="column" gap={0}>
|
||||
<div className={styles.marginTop}>
|
||||
<Stack direction="column" gap={1}>
|
||||
<Trans
|
||||
i18nKey="alerting.rule-form.evaluation.group-text"
|
||||
values={{ evaluateEvery: getValues('evaluateEvery') }}
|
||||
>
|
||||
All rules in the selected group are evaluated every {{ evaluateEvery: getValues('evaluateEvery') }}.
|
||||
</Trans>
|
||||
</Stack>
|
||||
</div>
|
||||
</Stack>
|
||||
</div>
|
||||
)}
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
function getStyles(theme: GrafanaTheme2) {
|
||||
return {
|
||||
formInput: css({
|
||||
flexGrow: 1,
|
||||
}),
|
||||
evaluationContainer: css({
|
||||
color: theme.colors.text.secondary,
|
||||
maxWidth: `${theme.breakpoints.values.sm}px`,
|
||||
fontSize: theme.typography.size.sm,
|
||||
}),
|
||||
marginTop: css({
|
||||
marginTop: theme.spacing(1),
|
||||
}),
|
||||
};
|
||||
}
|
||||
+1
-1
@@ -439,7 +439,7 @@ export function GrafanaEvaluationBehaviorStep({
|
||||
);
|
||||
}
|
||||
|
||||
function EvaluationGroupCreationModal({
|
||||
export function EvaluationGroupCreationModal({
|
||||
onClose,
|
||||
onCreate,
|
||||
groupfoldersForGrafana,
|
||||
|
||||
@@ -1079,9 +1079,6 @@
|
||||
"text-loading-notification-template": "Loading notification template",
|
||||
"title-error-loading-notification-template": "Error loading notification template"
|
||||
},
|
||||
"duration": {
|
||||
"immediately": "immediately"
|
||||
},
|
||||
"duration-quick-pick": {
|
||||
"none": "None"
|
||||
},
|
||||
@@ -2824,14 +2821,7 @@
|
||||
"continue-in-alerting": "Continue in Alerting",
|
||||
"create": "Create",
|
||||
"evaluation": {
|
||||
"after-breached": "after the condition is initially breached.",
|
||||
"custom-delay": "with custom delay of",
|
||||
"custom-delay-invalid": "The delay must be a multiple of the evaluation frequency.",
|
||||
"every": "Evaluate the rule every",
|
||||
"fire": "Fire the alert rule",
|
||||
"immediate-warning": "Immediate firing might lead to unnecessary alerts being sent for temporary issues",
|
||||
"wait-prefix": "Wait",
|
||||
"wait-suffix": "after the condition is breached before firing."
|
||||
"immediate-warning": "Immediate firing might lead to unnecessary alerts being sent for temporary issues"
|
||||
},
|
||||
"notification": {
|
||||
"contact-point": {
|
||||
|
||||
Reference in New Issue
Block a user