Alerting: defaults for simplified routing (#82050)

* Expand route settings by default when alert rule has values in these fields

* Default to manual routing option if FF is enabled and local storage is not set to false

* Add test for getDefautManualRouting function

* Update seting local storage item to false in case of policy routing

* Only save to local storage when creating a new alert rule
pull/82249/head
Sonia Aguilar 1 year ago committed by GitHub
parent 3e93a0991f
commit beca6a08b0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 17
      public/app/features/alerting/unified/components/rule-editor/alert-rule-form/AlertRuleForm.tsx
  2. 10
      public/app/features/alerting/unified/components/rule-editor/alert-rule-form/simplifiedRouting/AlertManagerRouting.tsx
  3. 33
      public/app/features/alerting/unified/utils/rule-form.test.ts
  4. 18
      public/app/features/alerting/unified/utils/rule-form.ts

@ -1,11 +1,11 @@
import { css } from '@emotion/css'; import { css } from '@emotion/css';
import React, { useEffect, useMemo, useState } from 'react'; import React, { useEffect, useMemo, useState } from 'react';
import { FormProvider, SubmitErrorHandler, useForm, UseFormWatch } from 'react-hook-form'; import { FormProvider, SubmitErrorHandler, UseFormWatch, useForm } from 'react-hook-form';
import { Link, useParams } from 'react-router-dom'; import { Link, useParams } from 'react-router-dom';
import { GrafanaTheme2 } from '@grafana/data'; import { GrafanaTheme2 } from '@grafana/data';
import { config } from '@grafana/runtime'; import { config } from '@grafana/runtime';
import { Button, ConfirmModal, CustomScrollbar, HorizontalGroup, Spinner, useStyles2, Stack } from '@grafana/ui'; import { Button, ConfirmModal, CustomScrollbar, HorizontalGroup, Spinner, Stack, useStyles2 } from '@grafana/ui';
import { AppChromeUpdate } from 'app/core/components/AppChrome/AppChromeUpdate'; import { AppChromeUpdate } from 'app/core/components/AppChrome/AppChromeUpdate';
import { useAppNotification } from 'app/core/copy/appNotification'; import { useAppNotification } from 'app/core/copy/appNotification';
import { contextSrv } from 'app/core/core'; import { contextSrv } from 'app/core/core';
@ -14,17 +14,18 @@ import { useQueryParams } from 'app/core/hooks/useQueryParams';
import { useDispatch } from 'app/types'; import { useDispatch } from 'app/types';
import { RuleWithLocation } from 'app/types/unified-alerting'; import { RuleWithLocation } from 'app/types/unified-alerting';
import { logInfo, LogMessages, trackNewAlerRuleFormError } from '../../../Analytics'; import { LogMessages, logInfo, trackNewAlerRuleFormError } from '../../../Analytics';
import { useUnifiedAlertingSelector } from '../../../hooks/useUnifiedAlertingSelector'; import { useUnifiedAlertingSelector } from '../../../hooks/useUnifiedAlertingSelector';
import { deleteRuleAction, saveRuleFormAction } from '../../../state/actions'; import { deleteRuleAction, saveRuleFormAction } from '../../../state/actions';
import { RuleFormType, RuleFormValues } from '../../../types/rule-form'; import { RuleFormType, RuleFormValues } from '../../../types/rule-form';
import { initialAsyncRequestState } from '../../../utils/redux'; import { initialAsyncRequestState } from '../../../utils/redux';
import { import {
MANUAL_ROUTING_KEY,
MINUTE,
formValuesFromExistingRule, formValuesFromExistingRule,
getDefaultFormValues, getDefaultFormValues,
getDefaultQueries, getDefaultQueries,
ignoreHiddenQueries, ignoreHiddenQueries,
MINUTE,
normalizeDefaultAnnotations, normalizeDefaultAnnotations,
} from '../../../utils/rule-form'; } from '../../../utils/rule-form';
import * as ruleId from '../../../utils/rule-id'; import * as ruleId from '../../../utils/rule-id';
@ -108,6 +109,14 @@ export const AlertRuleForm = ({ existing, prefill }: Props) => {
notifyApp.error(conditionErrorMsg); notifyApp.error(conditionErrorMsg);
return; return;
} }
// when creating a new rule, we save the manual routing setting in local storage
if (!existing) {
if (values.manualRouting) {
localStorage.setItem(MANUAL_ROUTING_KEY, 'true');
} else {
localStorage.setItem(MANUAL_ROUTING_KEY, 'false');
}
}
dispatch( dispatch(
saveRuleFormAction({ saveRuleFormAction({

@ -1,8 +1,10 @@
import { css } from '@emotion/css'; import { css } from '@emotion/css';
import React, { useState } from 'react'; import React, { useState } from 'react';
import { useFormContext } from 'react-hook-form';
import { GrafanaTheme2 } from '@grafana/data'; import { GrafanaTheme2 } from '@grafana/data';
import { Alert, CollapsableSection, LoadingPlaceholder, Stack, useStyles2 } from '@grafana/ui'; import { Alert, CollapsableSection, LoadingPlaceholder, Stack, useStyles2 } from '@grafana/ui';
import { RuleFormValues } from 'app/features/alerting/unified/types/rule-form';
import { AlertManagerDataSource } from 'app/features/alerting/unified/utils/datasource'; import { AlertManagerDataSource } from 'app/features/alerting/unified/utils/datasource';
import { ContactPointReceiverSummary } from '../../../contact-points/ContactPoints'; import { ContactPointReceiverSummary } from '../../../contact-points/ContactPoints';
@ -36,6 +38,12 @@ export function AlertManagerManualRouting({ alertManager }: AlertManagerManualRo
setSelectedContactPointWithMetadata(contactPoint); setSelectedContactPointWithMetadata(contactPoint);
}; };
const { watch } = useFormContext<RuleFormValues>();
const hasRouteSettings =
watch(`contactPoints.${alertManagerName}.overrideGrouping`) ||
watch(`contactPoints.${alertManagerName}.overrideTimings`) ||
watch(`contactPoints.${alertManagerName}.muteTimeIntervals`)?.length > 0;
const options = contactPoints.map((receiver) => { const options = contactPoints.map((receiver) => {
const integrations = receiver?.grafana_managed_receiver_configs; const integrations = receiver?.grafana_managed_receiver_configs;
const description = <ContactPointReceiverSummary receivers={integrations ?? []} />; const description = <ContactPointReceiverSummary receivers={integrations ?? []} />;
@ -74,7 +82,7 @@ export function AlertManagerManualRouting({ alertManager }: AlertManagerManualRo
<div className={styles.routingSection}> <div className={styles.routingSection}>
<CollapsableSection <CollapsableSection
label="Muting, grouping and timings (optional)" label="Muting, grouping and timings (optional)"
isOpen={false} isOpen={hasRouteSettings}
className={styles.collapsableSection} className={styles.collapsableSection}
> >
<Stack direction="column" gap={1}> <Stack direction="column" gap={1}>

@ -1,3 +1,4 @@
import { config } from '@grafana/runtime';
import { PromQuery } from 'app/plugins/datasource/prometheus/types'; import { PromQuery } from 'app/plugins/datasource/prometheus/types';
import { GrafanaAlertStateDecision, GrafanaRuleDefinition, RulerAlertingRuleDTO } from 'app/types/unified-alerting-dto'; import { GrafanaAlertStateDecision, GrafanaRuleDefinition, RulerAlertingRuleDTO } from 'app/types/unified-alerting-dto';
@ -5,11 +6,13 @@ import { AlertManagerManualRouting, RuleFormType, RuleFormValues } from '../type
import { GRAFANA_RULES_SOURCE_NAME } from './datasource'; import { GRAFANA_RULES_SOURCE_NAME } from './datasource';
import { import {
MANUAL_ROUTING_KEY,
alertingRulerRuleToRuleForm, alertingRulerRuleToRuleForm,
formValuesToRulerGrafanaRuleDTO, formValuesToRulerGrafanaRuleDTO,
formValuesToRulerRuleDTO, formValuesToRulerRuleDTO,
getContactPointsFromDTO, getContactPointsFromDTO,
getDefaultFormValues, getDefaultFormValues,
getDefautManualRouting,
getNotificationSettingsForDTO, getNotificationSettingsForDTO,
} from './rule-form'; } from './rule-form';
@ -207,3 +210,33 @@ describe('getNotificationSettingsForDTO', () => {
}); });
}); });
}); });
describe('getDefautManualRouting', () => {
afterEach(() => {
window.localStorage.clear();
});
it('returns false if the feature toggle is not enabled', () => {
config.featureToggles.alertingSimplifiedRouting = false;
expect(getDefautManualRouting()).toBe(false);
});
it('returns true if the feature toggle is enabled and localStorage is not set', () => {
config.featureToggles.alertingSimplifiedRouting = true;
expect(getDefautManualRouting()).toBe(true);
});
it('returns false if the feature toggle is enabled and localStorage is set to "false"', () => {
config.featureToggles.alertingSimplifiedRouting = true;
localStorage.setItem(MANUAL_ROUTING_KEY, 'false');
expect(getDefautManualRouting()).toBe(false);
});
it('returns true if the feature toggle is enabled and localStorage is set to any value other than "false"', () => {
config.featureToggles.alertingSimplifiedRouting = true;
localStorage.setItem(MANUAL_ROUTING_KEY, 'true');
expect(getDefautManualRouting()).toBe(true);
localStorage.removeItem(MANUAL_ROUTING_KEY);
expect(getDefautManualRouting()).toBe(true);
});
});

@ -11,7 +11,7 @@ import {
ScopedVars, ScopedVars,
TimeRange, TimeRange,
} from '@grafana/data'; } from '@grafana/data';
import { getDataSourceSrv } from '@grafana/runtime'; import { config, getDataSourceSrv } from '@grafana/runtime';
import { ExpressionDatasourceRef } from '@grafana/runtime/src/utils/DataSourceWithBackend'; import { ExpressionDatasourceRef } from '@grafana/runtime/src/utils/DataSourceWithBackend';
import { sceneGraph, VizPanel } from '@grafana/scenes'; import { sceneGraph, VizPanel } from '@grafana/scenes';
import { DataSourceJsonData } from '@grafana/schema'; import { DataSourceJsonData } from '@grafana/schema';
@ -54,6 +54,8 @@ export type PromOrLokiQuery = PromQuery | LokiQuery;
export const MINUTE = '1m'; export const MINUTE = '1m';
export const MANUAL_ROUTING_KEY = 'grafana.alerting.manualRouting';
export const getDefaultFormValues = (): RuleFormValues => { export const getDefaultFormValues = (): RuleFormValues => {
const { canCreateGrafanaRules, canCreateCloudRules } = getRulesAccess(); const { canCreateGrafanaRules, canCreateCloudRules } = getRulesAccess();
@ -75,7 +77,7 @@ export const getDefaultFormValues = (): RuleFormValues => {
execErrState: GrafanaAlertStateDecision.Error, execErrState: GrafanaAlertStateDecision.Error,
evaluateFor: '5m', evaluateFor: '5m',
evaluateEvery: MINUTE, evaluateEvery: MINUTE,
manualRouting: false, // let's decide this later manualRouting: getDefautManualRouting(), // we default to true if the feature toggle is enabled and the user hasn't set local storage to false
contactPoints: {}, contactPoints: {},
overrideGrouping: false, overrideGrouping: false,
overrideTimings: false, overrideTimings: false,
@ -89,6 +91,18 @@ export const getDefaultFormValues = (): RuleFormValues => {
}); });
}; };
export const getDefautManualRouting = () => {
// first check if feature toggle for simplified routing is enabled
const simplifiedRoutingToggleEnabled = config.featureToggles.alertingSimplifiedRouting ?? false;
if (!simplifiedRoutingToggleEnabled) {
return false;
}
//then, check in local storage if the user has enabled simplified routing
// if it's not set, we'll default to true
const manualRouting = localStorage.getItem(MANUAL_ROUTING_KEY);
return manualRouting !== 'false';
};
export function formValuesToRulerRuleDTO(values: RuleFormValues): RulerRuleDTO { export function formValuesToRulerRuleDTO(values: RuleFormValues): RulerRuleDTO {
const { name, expression, forTime, forTimeUnit, keepFiringForTime, keepFiringForTimeUnit, type } = values; const { name, expression, forTime, forTimeUnit, keepFiringForTime, keepFiringForTimeUnit, type } = values;
if (type === RuleFormType.cloudAlerting) { if (type === RuleFormType.cloudAlerting) {

Loading…
Cancel
Save