mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Simplified routing part3 (#79941)
* Update alert rule model in FE following BE design doc * Remove unnecessary conditional rendering * Update styles for optional route settings: add indentation * Update test * Add validation for grouBy to include grafana_folder and alertname * Split conversions between FEdataModel/ DTO, in separate functions * Update texts following Brenda's suggestions * Update text
This commit is contained in:
parent
48612063dd
commit
6f8ddac4eb
@ -80,9 +80,9 @@ export const NotificationsStep = ({ alertUid }: NotificationsStepProps) => {
|
|||||||
* - simplified routing is enabled
|
* - simplified routing is enabled
|
||||||
* - the alert rule is a grafana rule
|
* - the alert rule is a grafana rule
|
||||||
*
|
*
|
||||||
* This component will render the switch between the manual routing and the notification policy routing.
|
* This component will render the switch between the select contact point routing and the notification policy routing.
|
||||||
* It also renders the section body of the NotificationsStep, depending on the routing option selected.
|
* It also renders the section body of the NotificationsStep, depending on the routing option selected.
|
||||||
* If manual routing is selected, it will render the SimplifiedRouting component.
|
* If select contact point routing is selected, it will render the SimplifiedRouting component.
|
||||||
* If notification policy routing is selected, it will render the AutomaticRouting component.
|
* If notification policy routing is selected, it will render the AutomaticRouting component.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
@ -93,8 +93,8 @@ function ManualAndAutomaticRouting({ alertUid }: { alertUid?: string }) {
|
|||||||
const [manualRouting] = watch(['manualRouting']);
|
const [manualRouting] = watch(['manualRouting']);
|
||||||
|
|
||||||
const routingOptions = [
|
const routingOptions = [
|
||||||
{ label: 'Manually select contact point', value: RoutingOptions.ContactPoint },
|
{ label: 'Select contact point', value: RoutingOptions.ContactPoint },
|
||||||
{ label: 'Auto-select contact point', value: RoutingOptions.NotificationPolicy },
|
{ label: 'Use notification policy', value: RoutingOptions.NotificationPolicy },
|
||||||
];
|
];
|
||||||
|
|
||||||
const onRoutingOptionChange = (option: RoutingOptions) => {
|
const onRoutingOptionChange = (option: RoutingOptions) => {
|
||||||
@ -229,7 +229,7 @@ export const RoutingOptionDescription = ({ manualRouting }: NotificationsStepDes
|
|||||||
<Text variant="bodySmall" color="secondary">
|
<Text variant="bodySmall" color="secondary">
|
||||||
{manualRouting
|
{manualRouting
|
||||||
? 'Notifications for firing alerts are routed to a selected contact point.'
|
? 'Notifications for firing alerts are routed to a selected contact point.'
|
||||||
: 'Notifications for firing alerts are routed to contact points based on matching labels.'}
|
: 'Notifications for firing alerts are routed to contact points based on matching labels and the notification policy tree.'}
|
||||||
</Text>
|
</Text>
|
||||||
{manualRouting ? <NeedHelpInfoForContactpoint /> : <NeedHelpInfoForNotificationPolicy />}
|
{manualRouting ? <NeedHelpInfoForContactpoint /> : <NeedHelpInfoForNotificationPolicy />}
|
||||||
</div>
|
</div>
|
||||||
|
@ -23,7 +23,6 @@ export function AlertManagerManualRouting({ alertManager }: AlertManagerManualRo
|
|||||||
|
|
||||||
const alertManagerName = alertManager.name;
|
const alertManagerName = alertManager.name;
|
||||||
const { isLoading, error: errorInContactPointStatus, contactPoints } = useContactPointsWithStatus();
|
const { isLoading, error: errorInContactPointStatus, contactPoints } = useContactPointsWithStatus();
|
||||||
const shouldShowAM = true;
|
|
||||||
const [selectedContactPointWithMetadata, setSelectedContactPointWithMetadata] = useState<
|
const [selectedContactPointWithMetadata, setSelectedContactPointWithMetadata] = useState<
|
||||||
ContactPointWithMetadata | undefined
|
ContactPointWithMetadata | undefined
|
||||||
>();
|
>();
|
||||||
@ -36,17 +35,15 @@ export function AlertManagerManualRouting({ alertManager }: AlertManagerManualRo
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<Stack direction="column">
|
<Stack direction="column">
|
||||||
{shouldShowAM && (
|
<Stack direction="row" alignItems="center">
|
||||||
<Stack direction="row" alignItems="center">
|
<div className={styles.firstAlertManagerLine}></div>
|
||||||
<div className={styles.firstAlertManagerLine}></div>
|
<div className={styles.alertManagerName}>
|
||||||
<div className={styles.alertManagerName}>
|
Alert manager:
|
||||||
Alert manager:
|
<img src={alertManager.imgUrl} alt="Alert manager logo" className={styles.img} />
|
||||||
<img src={alertManager.imgUrl} alt="Alert manager logo" className={styles.img} />
|
{alertManagerName}
|
||||||
{alertManagerName}
|
</div>
|
||||||
</div>
|
<div className={styles.secondAlertManagerLine}></div>
|
||||||
<div className={styles.secondAlertManagerLine}></div>
|
</Stack>
|
||||||
</Stack>
|
|
||||||
)}
|
|
||||||
<Stack direction="row" gap={1} alignItems="center">
|
<Stack direction="row" gap={1} alignItems="center">
|
||||||
<ContactPointSelector
|
<ContactPointSelector
|
||||||
alertManager={alertManagerName}
|
alertManager={alertManagerName}
|
||||||
@ -59,7 +56,11 @@ export function AlertManagerManualRouting({ alertManager }: AlertManagerManualRo
|
|||||||
<ContactPointDetails receivers={selectedContactPointWithMetadata.grafana_managed_receiver_configs} />
|
<ContactPointDetails receivers={selectedContactPointWithMetadata.grafana_managed_receiver_configs} />
|
||||||
)}
|
)}
|
||||||
<div className={styles.routingSection}>
|
<div className={styles.routingSection}>
|
||||||
<CollapsableSection label="Muting, grouping and timings" isOpen={false} className={styles.collapsableSection}>
|
<CollapsableSection
|
||||||
|
label="Muting, grouping and timings (optional)"
|
||||||
|
isOpen={false}
|
||||||
|
className={styles.collapsableSection}
|
||||||
|
>
|
||||||
<Stack direction="column" gap={1}>
|
<Stack direction="column" gap={1}>
|
||||||
<MuteTimingFields alertManager={alertManagerName} />
|
<MuteTimingFields alertManager={alertManagerName} />
|
||||||
<RoutingSettings alertManager={alertManagerName} />
|
<RoutingSettings alertManager={alertManagerName} />
|
||||||
@ -74,7 +75,7 @@ function LinkToContactPoints() {
|
|||||||
return (
|
return (
|
||||||
<Link target="_blank" href={createUrl(hrefToContactPoints)} rel="noopener" aria-label="View alert rule">
|
<Link target="_blank" href={createUrl(hrefToContactPoints)} rel="noopener" aria-label="View alert rule">
|
||||||
<Stack direction="row" gap={1} alignItems="center" justifyContent="center">
|
<Stack direction="row" gap={1} alignItems="center" justifyContent="center">
|
||||||
<Text color="secondary">To browse contact points and create new ones go to</Text>
|
<Text color="secondary">To browse contact points and create new ones, go to</Text>
|
||||||
<Text color="link">Contact points</Text>
|
<Text color="link">Contact points</Text>
|
||||||
<Icon name={'external-link-alt'} size="sm" color="link" />
|
<Icon name={'external-link-alt'} size="sm" color="link" />
|
||||||
</Stack>
|
</Stack>
|
||||||
|
@ -24,7 +24,7 @@ export function MuteTimingFields({ alertManager }: MuteTimingFieldsProps) {
|
|||||||
<Field
|
<Field
|
||||||
label="Mute timings"
|
label="Mute timings"
|
||||||
data-testid="am-mute-timing-select"
|
data-testid="am-mute-timing-select"
|
||||||
description="Add mute timing to policy"
|
description="Select a mute timing to define when not to send notifications for this alert rule"
|
||||||
invalid={!!errors.contactPoints?.[alertManager]?.muteTimeIntervals}
|
invalid={!!errors.contactPoints?.[alertManager]?.muteTimeIntervals}
|
||||||
>
|
>
|
||||||
<InputControl
|
<InputControl
|
||||||
@ -35,6 +35,7 @@ export function MuteTimingFields({ alertManager }: MuteTimingFieldsProps) {
|
|||||||
className={styles.input}
|
className={styles.input}
|
||||||
onChange={(value) => onChange(mapMultiSelectValueToStrings(value))}
|
onChange={(value) => onChange(mapMultiSelectValueToStrings(value))}
|
||||||
options={muteTimingOptions}
|
options={muteTimingOptions}
|
||||||
|
placeholder="Select mute timings..."
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
control={control}
|
control={control}
|
||||||
|
@ -1,7 +1,19 @@
|
|||||||
|
import { css } from '@emotion/css';
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { useFormContext } from 'react-hook-form';
|
import { useFormContext } from 'react-hook-form';
|
||||||
|
|
||||||
import { Field, FieldValidationMessage, InputControl, MultiSelect, Stack, Switch, Text, useStyles2 } from '@grafana/ui';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
|
import {
|
||||||
|
Field,
|
||||||
|
FieldValidationMessage,
|
||||||
|
InlineField,
|
||||||
|
InputControl,
|
||||||
|
MultiSelect,
|
||||||
|
Stack,
|
||||||
|
Switch,
|
||||||
|
Text,
|
||||||
|
useStyles2,
|
||||||
|
} from '@grafana/ui';
|
||||||
import { useAlertmanagerConfig } from 'app/features/alerting/unified/hooks/useAlertmanagerConfig';
|
import { useAlertmanagerConfig } from 'app/features/alerting/unified/hooks/useAlertmanagerConfig';
|
||||||
import { useAlertmanager } from 'app/features/alerting/unified/state/AlertmanagerContext';
|
import { useAlertmanager } from 'app/features/alerting/unified/state/AlertmanagerContext';
|
||||||
import { RuleFormValues } from 'app/features/alerting/unified/types/rule-form';
|
import { RuleFormValues } from 'app/features/alerting/unified/types/rule-form';
|
||||||
@ -32,12 +44,14 @@ export const RoutingSettings = ({ alertManager }: RoutingSettingsProps) => {
|
|||||||
const { groupBy, groupIntervalValue, groupWaitValue, repeatIntervalValue } = useGetDefaultsForRoutingSettings();
|
const { groupBy, groupIntervalValue, groupWaitValue, repeatIntervalValue } = useGetDefaultsForRoutingSettings();
|
||||||
const overrideGrouping = watch(`contactPoints.${alertManager}.overrideGrouping`);
|
const overrideGrouping = watch(`contactPoints.${alertManager}.overrideGrouping`);
|
||||||
const overrideTimings = watch(`contactPoints.${alertManager}.overrideTimings`);
|
const overrideTimings = watch(`contactPoints.${alertManager}.overrideTimings`);
|
||||||
|
const requiredFieldsInGroupBy = ['grafana_folder', 'alertname'];
|
||||||
|
const styles = useStyles2(getStyles);
|
||||||
return (
|
return (
|
||||||
<Stack direction="column">
|
<Stack direction="column">
|
||||||
<Stack direction="row" gap={1} alignItems="center" justifyContent="space-between">
|
<Stack direction="row" gap={1} alignItems="center" justifyContent="space-between">
|
||||||
<Field label="Override grouping">
|
<InlineField label="Override grouping" transparent={true} className={styles.switchElement}>
|
||||||
<Switch id="override-grouping-toggle" {...register(`contactPoints.${alertManager}.overrideGrouping`)} />
|
<Switch id="override-grouping-toggle" {...register(`contactPoints.${alertManager}.overrideGrouping`)} />
|
||||||
</Field>
|
</InlineField>
|
||||||
{!overrideGrouping && (
|
{!overrideGrouping && (
|
||||||
<Text variant="body" color="secondary">
|
<Text variant="body" color="secondary">
|
||||||
Grouping: <strong>{groupBy.join(', ')}</strong>
|
Grouping: <strong>{groupBy.join(', ')}</strong>
|
||||||
@ -50,8 +64,22 @@ export const RoutingSettings = ({ alertManager }: RoutingSettingsProps) => {
|
|||||||
description="Group alerts when you receive a notification based on labels. If empty it will be inherited from the default notification policy."
|
description="Group alerts when you receive a notification based on labels. If empty it will be inherited from the default notification policy."
|
||||||
{...register(`contactPoints.${alertManager}.groupBy`, { required: true })}
|
{...register(`contactPoints.${alertManager}.groupBy`, { required: true })}
|
||||||
invalid={!!errors.contactPoints?.[alertManager]?.groupBy}
|
invalid={!!errors.contactPoints?.[alertManager]?.groupBy}
|
||||||
|
className={styles.optionalContent}
|
||||||
>
|
>
|
||||||
<InputControl
|
<InputControl
|
||||||
|
rules={{
|
||||||
|
validate: (value: string[]) => {
|
||||||
|
if (!value || value.length === 0) {
|
||||||
|
return 'At least one group by option is required.';
|
||||||
|
}
|
||||||
|
// we need to make sure that the required fields are included
|
||||||
|
const requiredFieldsIncluded = requiredFieldsInGroupBy.every((field) => value.includes(field));
|
||||||
|
if (!requiredFieldsIncluded) {
|
||||||
|
return `Group by must include ${requiredFieldsInGroupBy.join(', ')}`;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
}}
|
||||||
render={({ field: { onChange, ref, ...field }, fieldState: { error } }) => (
|
render={({ field: { onChange, ref, ...field }, fieldState: { error } }) => (
|
||||||
<>
|
<>
|
||||||
<MultiSelect
|
<MultiSelect
|
||||||
@ -68,7 +96,7 @@ export const RoutingSettings = ({ alertManager }: RoutingSettingsProps) => {
|
|||||||
onChange={(value) => onChange(mapMultiSelectValueToStrings(value))}
|
onChange={(value) => onChange(mapMultiSelectValueToStrings(value))}
|
||||||
options={[...commonGroupByOptions, ...groupByOptions]}
|
options={[...commonGroupByOptions, ...groupByOptions]}
|
||||||
/>
|
/>
|
||||||
{error && <FieldValidationMessage>{'At least one group by option is required'}</FieldValidationMessage>}
|
{error && <FieldValidationMessage>{error.message}</FieldValidationMessage>}
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
name={`contactPoints.${alertManager}.groupBy`}
|
name={`contactPoints.${alertManager}.groupBy`}
|
||||||
@ -77,9 +105,9 @@ export const RoutingSettings = ({ alertManager }: RoutingSettingsProps) => {
|
|||||||
</Field>
|
</Field>
|
||||||
)}
|
)}
|
||||||
<Stack direction="row" gap={1} alignItems="center" justifyContent="space-between">
|
<Stack direction="row" gap={1} alignItems="center" justifyContent="space-between">
|
||||||
<Field label="Override timings">
|
<InlineField label="Override timings" transparent={true} className={styles.switchElement}>
|
||||||
<Switch id="override-timings-toggle" {...register(`contactPoints.${alertManager}.overrideTimings`)} />
|
<Switch id="override-timings-toggle" {...register(`contactPoints.${alertManager}.overrideTimings`)} />
|
||||||
</Field>
|
</InlineField>
|
||||||
{!overrideTimings && (
|
{!overrideTimings && (
|
||||||
<Text variant="body" color="secondary">
|
<Text variant="body" color="secondary">
|
||||||
Group wait: <strong>{groupWaitValue}, </strong>
|
Group wait: <strong>{groupWaitValue}, </strong>
|
||||||
@ -88,7 +116,11 @@ export const RoutingSettings = ({ alertManager }: RoutingSettingsProps) => {
|
|||||||
</Text>
|
</Text>
|
||||||
)}
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
{overrideTimings && <RouteTimings alertManager={alertManager} />}
|
{overrideTimings && (
|
||||||
|
<div className={styles.optionalContent}>
|
||||||
|
<RouteTimings alertManager={alertManager} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -106,3 +138,14 @@ function useGetDefaultsForRoutingSettings() {
|
|||||||
};
|
};
|
||||||
}, [config]);
|
}, [config]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getStyles = (theme: GrafanaTheme2) => ({
|
||||||
|
switchElement: css({
|
||||||
|
flexFlow: 'row-reverse',
|
||||||
|
gap: theme.spacing(1),
|
||||||
|
alignItems: 'center',
|
||||||
|
}),
|
||||||
|
optionalContent: css({
|
||||||
|
marginLeft: '49px',
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
@ -10,11 +10,11 @@ exports[`formValuesToRulerGrafanaRuleDTO should correctly convert rule form valu
|
|||||||
"for": "5m",
|
"for": "5m",
|
||||||
"grafana_alert": {
|
"grafana_alert": {
|
||||||
"condition": "A",
|
"condition": "A",
|
||||||
"contactPoints": undefined,
|
|
||||||
"data": [],
|
"data": [],
|
||||||
"exec_err_state": "Error",
|
"exec_err_state": "Error",
|
||||||
"is_paused": false,
|
"is_paused": false,
|
||||||
"no_data_state": "NoData",
|
"no_data_state": "NoData",
|
||||||
|
"notification_settings": undefined,
|
||||||
"title": "",
|
"title": "",
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
@ -33,7 +33,6 @@ exports[`formValuesToRulerGrafanaRuleDTO should not save both instant and range
|
|||||||
"for": "5m",
|
"for": "5m",
|
||||||
"grafana_alert": {
|
"grafana_alert": {
|
||||||
"condition": "A",
|
"condition": "A",
|
||||||
"contactPoints": undefined,
|
|
||||||
"data": [
|
"data": [
|
||||||
{
|
{
|
||||||
"datasourceUid": "dsuid",
|
"datasourceUid": "dsuid",
|
||||||
@ -54,6 +53,7 @@ exports[`formValuesToRulerGrafanaRuleDTO should not save both instant and range
|
|||||||
"exec_err_state": "Error",
|
"exec_err_state": "Error",
|
||||||
"is_paused": false,
|
"is_paused": false,
|
||||||
"no_data_state": "NoData",
|
"no_data_state": "NoData",
|
||||||
|
"notification_settings": undefined,
|
||||||
"title": "",
|
"title": "",
|
||||||
},
|
},
|
||||||
"labels": {
|
"labels": {
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
import { PromQuery } from 'app/plugins/datasource/prometheus/types';
|
import { PromQuery } from 'app/plugins/datasource/prometheus/types';
|
||||||
import { RulerAlertingRuleDTO } from 'app/types/unified-alerting-dto';
|
import { GrafanaAlertStateDecision, GrafanaRuleDefinition, RulerAlertingRuleDTO } from 'app/types/unified-alerting-dto';
|
||||||
|
|
||||||
import { RuleFormType, RuleFormValues } from '../types/rule-form';
|
import { AlertManagerManualRouting, RuleFormType, RuleFormValues } from '../types/rule-form';
|
||||||
|
|
||||||
|
import { GRAFANA_RULES_SOURCE_NAME } from './datasource';
|
||||||
import {
|
import {
|
||||||
alertingRulerRuleToRuleForm,
|
alertingRulerRuleToRuleForm,
|
||||||
formValuesToRulerGrafanaRuleDTO,
|
formValuesToRulerGrafanaRuleDTO,
|
||||||
formValuesToRulerRuleDTO,
|
formValuesToRulerRuleDTO,
|
||||||
|
getContactPointsFromDTO,
|
||||||
getDefaultFormValues,
|
getDefaultFormValues,
|
||||||
|
getNotificationSettingsForDTO,
|
||||||
} from './rule-form';
|
} from './rule-form';
|
||||||
|
|
||||||
describe('formValuesToRulerGrafanaRuleDTO', () => {
|
describe('formValuesToRulerGrafanaRuleDTO', () => {
|
||||||
@ -85,3 +88,122 @@ describe('formValuesToRulerGrafanaRuleDTO', () => {
|
|||||||
expect(alertingRulerRuleToRuleForm(rule)).toMatchSnapshot();
|
expect(alertingRulerRuleToRuleForm(rule)).toMatchSnapshot();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
describe('getContactPointsFromDTO', () => {
|
||||||
|
it('should return undefined if notification_settings is not defined', () => {
|
||||||
|
const ga: GrafanaRuleDefinition = {
|
||||||
|
uid: '123',
|
||||||
|
title: 'myalert',
|
||||||
|
namespace_uid: '123',
|
||||||
|
condition: 'A',
|
||||||
|
no_data_state: GrafanaAlertStateDecision.Alerting,
|
||||||
|
exec_err_state: GrafanaAlertStateDecision.Alerting,
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
datasourceUid: '123',
|
||||||
|
refId: 'A',
|
||||||
|
queryType: 'huh',
|
||||||
|
model: { refId: 'A' },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
notification_settings: undefined,
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = getContactPointsFromDTO(ga);
|
||||||
|
expect(result).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return routingSettings with correct props if notification_settings is defined', () => {
|
||||||
|
const ga: GrafanaRuleDefinition = {
|
||||||
|
uid: '123',
|
||||||
|
title: 'myalert',
|
||||||
|
namespace_uid: '123',
|
||||||
|
condition: 'A',
|
||||||
|
no_data_state: GrafanaAlertStateDecision.Alerting,
|
||||||
|
exec_err_state: GrafanaAlertStateDecision.Alerting,
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
datasourceUid: '123',
|
||||||
|
refId: 'A',
|
||||||
|
queryType: 'huh',
|
||||||
|
model: { refId: 'A' },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
notification_settings: {
|
||||||
|
receiver: 'receiver',
|
||||||
|
mute_timings: ['mute_timing'],
|
||||||
|
group_by: ['group_by'],
|
||||||
|
group_wait: 'group_wait',
|
||||||
|
group_interval: 'group_interval',
|
||||||
|
repeat_interval: 'repeat_interval',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = getContactPointsFromDTO(ga);
|
||||||
|
expect(result).toEqual({
|
||||||
|
[GRAFANA_RULES_SOURCE_NAME]: {
|
||||||
|
selectedContactPoint: 'receiver',
|
||||||
|
muteTimeIntervals: ['mute_timing'],
|
||||||
|
overrideGrouping: true,
|
||||||
|
overrideTimings: true,
|
||||||
|
groupBy: ['group_by'],
|
||||||
|
groupWaitValue: 'group_wait',
|
||||||
|
groupIntervalValue: 'group_interval',
|
||||||
|
repeatIntervalValue: 'repeat_interval',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getNotificationSettingsForDTO', () => {
|
||||||
|
it('should return undefined if manualRouting is false', () => {
|
||||||
|
const manualRouting = false;
|
||||||
|
const contactPoints: AlertManagerManualRouting = {
|
||||||
|
grafana: {
|
||||||
|
selectedContactPoint: 'receiver',
|
||||||
|
muteTimeIntervals: ['mute_timing'],
|
||||||
|
overrideGrouping: true,
|
||||||
|
overrideTimings: true,
|
||||||
|
groupBy: ['group_by'],
|
||||||
|
groupWaitValue: 'group_wait',
|
||||||
|
groupIntervalValue: 'group_interval',
|
||||||
|
repeatIntervalValue: 'repeat_interval',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = getNotificationSettingsForDTO(manualRouting, contactPoints);
|
||||||
|
expect(result).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return undefined if selectedContactPoint is not defined', () => {
|
||||||
|
const manualRouting = true;
|
||||||
|
|
||||||
|
const result = getNotificationSettingsForDTO(manualRouting, undefined);
|
||||||
|
expect(result).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return notification settings if manualRouting is true and selectedContactPoint is defined', () => {
|
||||||
|
const manualRouting = true;
|
||||||
|
const contactPoints: AlertManagerManualRouting = {
|
||||||
|
grafana: {
|
||||||
|
selectedContactPoint: 'receiver',
|
||||||
|
muteTimeIntervals: ['mute_timing'],
|
||||||
|
overrideGrouping: true,
|
||||||
|
overrideTimings: true,
|
||||||
|
groupBy: ['group_by'],
|
||||||
|
groupWaitValue: 'group_wait',
|
||||||
|
groupIntervalValue: 'group_interval',
|
||||||
|
repeatIntervalValue: 'repeat_interval',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = getNotificationSettingsForDTO(manualRouting, contactPoints);
|
||||||
|
expect(result).toEqual({
|
||||||
|
receiver: 'receiver',
|
||||||
|
mute_timings: ['mute_timing'],
|
||||||
|
group_by: ['group_by'],
|
||||||
|
group_wait: 'group_wait',
|
||||||
|
group_interval: 'group_interval',
|
||||||
|
repeat_interval: 'repeat_interval',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@ -25,6 +25,8 @@ import {
|
|||||||
AlertQuery,
|
AlertQuery,
|
||||||
Annotations,
|
Annotations,
|
||||||
GrafanaAlertStateDecision,
|
GrafanaAlertStateDecision,
|
||||||
|
GrafanaNotificationSettings,
|
||||||
|
GrafanaRuleDefinition,
|
||||||
Labels,
|
Labels,
|
||||||
PostableRuleGrafanaRuleDTO,
|
PostableRuleGrafanaRuleDTO,
|
||||||
RulerAlertingRuleDTO,
|
RulerAlertingRuleDTO,
|
||||||
@ -33,11 +35,11 @@ import {
|
|||||||
} from 'app/types/unified-alerting-dto';
|
} from 'app/types/unified-alerting-dto';
|
||||||
|
|
||||||
import { EvalFunction } from '../../state/alertDef';
|
import { EvalFunction } from '../../state/alertDef';
|
||||||
import { RuleFormType, RuleFormValues } from '../types/rule-form';
|
import { AlertManagerManualRouting, ContactPoint, RuleFormType, RuleFormValues } from '../types/rule-form';
|
||||||
|
|
||||||
import { getRulesAccess } from './access-control';
|
import { getRulesAccess } from './access-control';
|
||||||
import { Annotation, defaultAnnotations } from './constants';
|
import { Annotation, defaultAnnotations } from './constants';
|
||||||
import { getDefaultOrFirstCompatibleDataSource, isGrafanaRulesSource } from './datasource';
|
import { getDefaultOrFirstCompatibleDataSource, GRAFANA_RULES_SOURCE_NAME, isGrafanaRulesSource } from './datasource';
|
||||||
import { arrayToRecord, recordToArray } from './misc';
|
import { arrayToRecord, recordToArray } from './misc';
|
||||||
import { isAlertingRulerRule, isGrafanaRulerRule, isRecordingRulerRule } from './rules';
|
import { isAlertingRulerRule, isGrafanaRulerRule, isRecordingRulerRule } from './rules';
|
||||||
import { parseInterval } from './time';
|
import { parseInterval } from './time';
|
||||||
@ -138,10 +140,34 @@ export function normalizeDefaultAnnotations(annotations: Array<{ key: string; va
|
|||||||
return orderedAnnotations;
|
return orderedAnnotations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getNotificationSettingsForDTO(
|
||||||
|
manualRouting: boolean,
|
||||||
|
contactPoints?: AlertManagerManualRouting
|
||||||
|
): GrafanaNotificationSettings | undefined {
|
||||||
|
if (contactPoints?.grafana?.selectedContactPoint && manualRouting) {
|
||||||
|
return {
|
||||||
|
receiver: contactPoints?.grafana?.selectedContactPoint,
|
||||||
|
mute_timings: contactPoints?.grafana?.muteTimeIntervals,
|
||||||
|
group_by: contactPoints?.grafana?.overrideGrouping ? contactPoints?.grafana?.groupBy : undefined,
|
||||||
|
group_wait: contactPoints?.grafana?.overrideTimings ? contactPoints?.grafana?.groupWaitValue : undefined,
|
||||||
|
group_interval: contactPoints?.grafana?.overrideTimings ? contactPoints?.grafana?.groupIntervalValue : undefined,
|
||||||
|
repeat_interval: contactPoints?.grafana?.overrideTimings
|
||||||
|
? contactPoints?.grafana?.repeatIntervalValue
|
||||||
|
: undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
export function formValuesToRulerGrafanaRuleDTO(values: RuleFormValues): PostableRuleGrafanaRuleDTO {
|
export function formValuesToRulerGrafanaRuleDTO(values: RuleFormValues): PostableRuleGrafanaRuleDTO {
|
||||||
const { name, condition, noDataState, execErrState, evaluateFor, queries, isPaused, contactPoints, manualRouting } =
|
const { name, condition, noDataState, execErrState, evaluateFor, queries, isPaused, contactPoints, manualRouting } =
|
||||||
values;
|
values;
|
||||||
if (condition) {
|
if (condition) {
|
||||||
|
const notificationSettings: GrafanaNotificationSettings | undefined = getNotificationSettingsForDTO(
|
||||||
|
manualRouting,
|
||||||
|
contactPoints
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
grafana_alert: {
|
grafana_alert: {
|
||||||
title: name,
|
title: name,
|
||||||
@ -150,7 +176,7 @@ export function formValuesToRulerGrafanaRuleDTO(values: RuleFormValues): Postabl
|
|||||||
exec_err_state: execErrState,
|
exec_err_state: execErrState,
|
||||||
data: queries.map(fixBothInstantAndRangeQuery),
|
data: queries.map(fixBothInstantAndRangeQuery),
|
||||||
is_paused: Boolean(isPaused),
|
is_paused: Boolean(isPaused),
|
||||||
contactPoints: manualRouting ? contactPoints : undefined,
|
notification_settings: notificationSettings,
|
||||||
},
|
},
|
||||||
for: evaluateFor,
|
for: evaluateFor,
|
||||||
annotations: arrayToRecord(values.annotations || []),
|
annotations: arrayToRecord(values.annotations || []),
|
||||||
@ -160,6 +186,27 @@ export function formValuesToRulerGrafanaRuleDTO(values: RuleFormValues): Postabl
|
|||||||
throw new Error('Cannot create rule without specifying alert condition');
|
throw new Error('Cannot create rule without specifying alert condition');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getContactPointsFromDTO(ga: GrafanaRuleDefinition): AlertManagerManualRouting | undefined {
|
||||||
|
const contactPoint: ContactPoint | undefined = ga.notification_settings
|
||||||
|
? {
|
||||||
|
selectedContactPoint: ga.notification_settings.receiver,
|
||||||
|
muteTimeIntervals: ga.notification_settings.mute_timings ?? [],
|
||||||
|
overrideGrouping: Boolean(ga.notification_settings?.group_by),
|
||||||
|
overrideTimings: Boolean(ga.notification_settings.group_wait),
|
||||||
|
groupBy: ga.notification_settings.group_by || [],
|
||||||
|
groupWaitValue: ga.notification_settings.group_wait || '',
|
||||||
|
groupIntervalValue: ga.notification_settings.group_interval || '',
|
||||||
|
repeatIntervalValue: ga.notification_settings.repeat_interval || '',
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
const routingSettings: AlertManagerManualRouting | undefined = contactPoint
|
||||||
|
? {
|
||||||
|
[GRAFANA_RULES_SOURCE_NAME]: contactPoint,
|
||||||
|
}
|
||||||
|
: undefined;
|
||||||
|
return routingSettings;
|
||||||
|
}
|
||||||
|
|
||||||
export function rulerRuleToFormValues(ruleWithLocation: RuleWithLocation): RuleFormValues {
|
export function rulerRuleToFormValues(ruleWithLocation: RuleWithLocation): RuleFormValues {
|
||||||
const { ruleSourceName, namespace, group, rule } = ruleWithLocation;
|
const { ruleSourceName, namespace, group, rule } = ruleWithLocation;
|
||||||
|
|
||||||
@ -168,6 +215,8 @@ export function rulerRuleToFormValues(ruleWithLocation: RuleWithLocation): RuleF
|
|||||||
if (isGrafanaRulerRule(rule)) {
|
if (isGrafanaRulerRule(rule)) {
|
||||||
const ga = rule.grafana_alert;
|
const ga = rule.grafana_alert;
|
||||||
|
|
||||||
|
const routingSettings: AlertManagerManualRouting | undefined = getContactPointsFromDTO(ga);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...defaultFormValues,
|
...defaultFormValues,
|
||||||
name: ga.title,
|
name: ga.title,
|
||||||
@ -183,8 +232,9 @@ export function rulerRuleToFormValues(ruleWithLocation: RuleWithLocation): RuleF
|
|||||||
labels: listifyLabelsOrAnnotations(rule.labels, true),
|
labels: listifyLabelsOrAnnotations(rule.labels, true),
|
||||||
folder: { title: namespace, uid: ga.namespace_uid },
|
folder: { title: namespace, uid: ga.namespace_uid },
|
||||||
isPaused: ga.is_paused,
|
isPaused: ga.is_paused,
|
||||||
contactPoints: ga.contactPoints,
|
|
||||||
manualRouting: Boolean(ga.contactPoints),
|
contactPoints: routingSettings,
|
||||||
|
manualRouting: Boolean(routingSettings),
|
||||||
// next line is for testing
|
// next line is for testing
|
||||||
// manualRouting: true,
|
// manualRouting: true,
|
||||||
// contactPoints: {
|
// contactPoints: {
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
// Prometheus API DTOs, possibly to be autogenerated from openapi spec in the near future
|
// Prometheus API DTOs, possibly to be autogenerated from openapi spec in the near future
|
||||||
|
|
||||||
import { DataQuery, RelativeTimeRange } from '@grafana/data';
|
import { DataQuery, RelativeTimeRange } from '@grafana/data';
|
||||||
import { AlertManagerManualRouting } from 'app/features/alerting/unified/types/rule-form';
|
|
||||||
|
|
||||||
import { AlertGroupTotals } from './unified-alerting';
|
import { AlertGroupTotals } from './unified-alerting';
|
||||||
|
|
||||||
@ -198,6 +197,14 @@ export interface AlertQuery {
|
|||||||
model: AlertDataQuery;
|
model: AlertDataQuery;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface GrafanaNotificationSettings {
|
||||||
|
receiver: string;
|
||||||
|
group_by?: string[];
|
||||||
|
group_wait?: string;
|
||||||
|
group_interval?: string;
|
||||||
|
repeat_interval?: string;
|
||||||
|
mute_timings?: string[];
|
||||||
|
}
|
||||||
export interface PostableGrafanaRuleDefinition {
|
export interface PostableGrafanaRuleDefinition {
|
||||||
uid?: string;
|
uid?: string;
|
||||||
title: string;
|
title: string;
|
||||||
@ -206,7 +213,7 @@ export interface PostableGrafanaRuleDefinition {
|
|||||||
exec_err_state: GrafanaAlertStateDecision;
|
exec_err_state: GrafanaAlertStateDecision;
|
||||||
data: AlertQuery[];
|
data: AlertQuery[];
|
||||||
is_paused?: boolean;
|
is_paused?: boolean;
|
||||||
contactPoints?: AlertManagerManualRouting;
|
notification_settings?: GrafanaNotificationSettings;
|
||||||
}
|
}
|
||||||
export interface GrafanaRuleDefinition extends PostableGrafanaRuleDefinition {
|
export interface GrafanaRuleDefinition extends PostableGrafanaRuleDefinition {
|
||||||
id?: string;
|
id?: string;
|
||||||
|
Loading…
Reference in New Issue
Block a user