Unified Alerting: UI explains "match all" case in Notification Policies. (#47574)

This change makes it explicit in the UI that a notification policy with no matching labels matches all alerts that it processes. There are visual changes in both the Notification Policy editor, and in the Notification Policy table where matching columns are shown.

It's valid to have a notification policy with no label matchers attached. Such a policy matches all alerts sent to it. It's a common stumbling block for users. Where are all my alerts going?

Co-authored-by: gillesdemey <gilles.de.mey@gmail.com>
pull/47674/head
Joe Blubaugh 3 years ago committed by GitHub
parent 7a8437020d
commit 51c98b182d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 149
      public/app/features/alerting/unified/components/amroutes/AmRoutesExpandedForm.tsx
  2. 8
      public/app/features/alerting/unified/components/amroutes/AmRoutesTable.tsx

@ -14,6 +14,8 @@ import {
Select,
Switch,
useStyles2,
Badge,
VerticalGroup,
} from '@grafana/ui';
import { AmRouteReceiver, FormAmRoute } from '../../types/amroutes';
import {
@ -56,73 +58,85 @@ export const AmRoutesExpandedForm: FC<AmRoutesExpandedFormProps> = ({ onCancel,
<FieldArray name="object_matchers" control={control}>
{({ fields, append, remove }) => (
<>
<div>Matching labels</div>
<div className={styles.matchersContainer}>
{fields.map((field, index) => {
const localPath = `object_matchers[${index}]`;
return (
<HorizontalGroup key={field.id} align="flex-start">
<Field
label="Label"
invalid={!!errors.object_matchers?.[index]?.name}
error={errors.object_matchers?.[index]?.name?.message}
>
<Input
{...register(`${localPath}.name`, { required: 'Field is required' })}
defaultValue={field.name}
placeholder="label"
/>
</Field>
<Field label={'Operator'}>
<InputControl
render={({ field: { onChange, ref, ...field } }) => (
<Select
{...field}
className={styles.matchersOperator}
onChange={(value) => onChange(value?.value)}
options={matcherFieldOptions}
aria-label="Operator"
menuShouldPortal
<VerticalGroup justify="flex-start" spacing="md">
<div>Matching labels</div>
{fields.length === 0 && (
<Badge
color="orange"
className={styles.noMatchersWarning}
icon="exclamation-triangle"
text="If no matchers are specified, this notification policy will handle all alert instances."
/>
)}
{fields.length > 0 && (
<div className={styles.matchersContainer}>
{fields.map((field, index) => {
const localPath = `object_matchers[${index}]`;
return (
<HorizontalGroup key={field.id} align="flex-start">
<Field
label="Label"
invalid={!!errors.object_matchers?.[index]?.name}
error={errors.object_matchers?.[index]?.name?.message}
>
<Input
{...register(`${localPath}.name`, { required: 'Field is required' })}
defaultValue={field.name}
placeholder="label"
/>
</Field>
<Field label={'Operator'}>
<InputControl
render={({ field: { onChange, ref, ...field } }) => (
<Select
{...field}
className={styles.matchersOperator}
onChange={(value) => onChange(value?.value)}
options={matcherFieldOptions}
aria-label="Operator"
menuShouldPortal
/>
)}
defaultValue={field.operator}
control={control}
name={`${localPath}.operator` as const}
rules={{ required: { value: true, message: 'Required.' } }}
/>
)}
defaultValue={field.operator}
control={control}
name={`${localPath}.operator` as const}
rules={{ required: { value: true, message: 'Required.' } }}
/>
</Field>
<Field
label="Value"
invalid={!!errors.object_matchers?.[index]?.value}
error={errors.object_matchers?.[index]?.value?.message}
>
<Input
{...register(`${localPath}.value`, { required: 'Field is required' })}
defaultValue={field.value}
placeholder="value"
/>
</Field>
<IconButton
className={styles.removeButton}
tooltip="Remove matcher"
name={'trash-alt'}
onClick={() => remove(index)}
>
Remove
</IconButton>
</HorizontalGroup>
);
})}
</div>
<Button
className={styles.addMatcherBtn}
icon="plus"
onClick={() => append(emptyArrayFieldMatcher)}
variant="secondary"
type="button"
>
Add matcher
</Button>
</Field>
<Field
label="Value"
invalid={!!errors.object_matchers?.[index]?.value}
error={errors.object_matchers?.[index]?.value?.message}
>
<Input
{...register(`${localPath}.value`, { required: 'Field is required' })}
defaultValue={field.value}
placeholder="value"
/>
</Field>
<IconButton
className={styles.removeButton}
tooltip="Remove matcher"
name={'trash-alt'}
onClick={() => remove(index)}
>
Remove
</IconButton>
</HorizontalGroup>
);
})}
</div>
)}
<Button
className={styles.addMatcherBtn}
icon="plus"
onClick={() => append(emptyArrayFieldMatcher)}
variant="secondary"
type="button"
>
Add matcher
</Button>
</VerticalGroup>
</>
)}
</FieldArray>
@ -376,5 +390,8 @@ const getStyles = (theme: GrafanaTheme2) => {
margin-left: ${theme.spacing(1.5)};
}
`,
noMatchersWarning: css`
padding: ${theme.spacing(1)} ${theme.spacing(2)};
`,
};
};

@ -90,7 +90,13 @@ export const AmRoutesTable: FC<AmRoutesTableProps> = ({
id: 'matchingCriteria',
label: 'Matching labels',
// eslint-disable-next-line react/display-name
renderCell: (item) => <Matchers matchers={item.data.object_matchers.map(matcherFieldToMatcher)} />,
renderCell: (item) => {
return item.data.object_matchers.length ? (
<Matchers matchers={item.data.object_matchers.map(matcherFieldToMatcher)} />
) : (
<span>Matches all alert instances</span>
);
},
size: 10,
},
{

Loading…
Cancel
Save